├── Gemfile ├── merge-hashes.rb ├── the-hashes.rb ├── tally_authors.rb ├── determine_authors.sh ├── license.md ├── file_stats.sh └── readme.md /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem 'flyrb' 4 | gem 'awesome_print' 5 | 6 | -------------------------------------------------------------------------------- /merge-hashes.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'awesome_print' 3 | require './the-hashes' 4 | 5 | total_commit_matrix = THE_HASHES.inject(Hash.new(0)) do |memo, commit_matrix| 6 | commit_matrix.each do |committer, commits| 7 | memo[committer] += commits 8 | end 9 | 10 | memo 11 | end 12 | 13 | ap total_commit_matrix 14 | 15 | -------------------------------------------------------------------------------- /the-hashes.rb: -------------------------------------------------------------------------------- 1 | THE_HASHES = [] 2 | 3 | # fill this constant with the hashes generated by running tally_authors against multiple languages. 4 | # example: 5 | # for language in json coffee jade md cson styl; 6 | # do ruby tally_authors.rb <(cd ../projectname/ && ~/code/rewind/determine_authors.sh $language); 7 | # done 8 | # then copy-paste the stack of hashes in here and push each into the constant 9 | 10 | # example 11 | THE_HASHES << { 12 | "developer's name" => 16, 13 | "other developer's name" => 19 14 | } 15 | 16 | -------------------------------------------------------------------------------- /tally_authors.rb: -------------------------------------------------------------------------------- 1 | # this file parses git log scrapes of commit authors and counts them. 2 | # in some cases it gives you a very primitive metric for authorship and 3 | # authority; however these are definitely numbers to consider with a 4 | # grain of salt. 5 | 6 | require 'rubygems' 7 | require 'awesome_print' 8 | 9 | filename = ARGV[0] 10 | authorship = {} 11 | 12 | File.open(filename).each_line do |line| 13 | author = line.gsub(/Author: /, "").chomp.split.tap(&:pop).join(' ').downcase 14 | if authorship[author] 15 | authorship[author] += 1 16 | else 17 | authorship[author] = 1 18 | end 19 | end 20 | 21 | ap authorship 22 | 23 | -------------------------------------------------------------------------------- /determine_authors.sh: -------------------------------------------------------------------------------- 1 | # determine who are the key authors on the site; this file collects the data, another 2 | # (Ruby) file sorts it to determine an **extremely** primitive measure of authority 3 | 4 | # usage: determine_authors.sh [filetype] 5 | # 6 | # e.g. determine_authors.sh "js" # examines authorship for *.js files 7 | # determine_authors.sh "handlebars" 8 | # determine_authors.sh "ruby" 9 | # determine_authors.sh "et_cetera" 10 | 11 | function generate_authors_file { 12 | for filename in $(git ls-tree --full-tree -r --name-only HEAD | grep ".*\.$1"); do 13 | git log $filename | grep Author; 14 | done 15 | } 16 | 17 | generate_authors_file $1 18 | 19 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2011-2015 Giles Bowkett 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /file_stats.sh: -------------------------------------------------------------------------------- 1 | function git_history { 2 | git log $filename | grep "Date: " 3 | } 4 | 5 | function first_commit { 6 | git_history | tail -1 7 | } 8 | 9 | function last_commit { 10 | git_history | head -1 11 | } 12 | 13 | function number_of_commits { 14 | git_history | wc -l 15 | } 16 | 17 | function legible_output { 18 | # $2: filename 19 | # $1: lines of code 20 | # $3: number of commits 21 | # $6: month (first) 22 | # $7: date (first) 23 | # $9: year (first) 24 | # $13: month (last) 25 | # $14: date (last) 26 | # $16: year (last) 27 | awk '{print $2 "," $1 "," $3 "," $6 " " $7 " " $9 "," $13 " " $14 " " $16}' 28 | } 29 | 30 | function lines_of_code { 31 | echo $(wc -l $1) | sed 's/\(\/.*\)[ ]/\1_/' 32 | } 33 | 34 | function csv_lines_for { 35 | 36 | # allow spaces in filenames, via file separator; re PR #7, Issue #5 37 | STORED_IFS=$IFS 38 | IFS=$(echo -en "\n\b") 39 | 40 | for filename in $(find . -iname "*.$1"); do 41 | echo "$(lines_of_code $filename) $(number_of_commits) $(first_commit) $(last_commit)" | 42 | legible_output | 43 | xargs echo 44 | done 45 | 46 | IFS=$STORED_IFS 47 | 48 | } 49 | 50 | function create_csv { 51 | echo "filename,lines of code,number of commits,date of first commit,date of last commit" 52 | 53 | for argument in "$@" 54 | do 55 | csv_lines_for $argument 56 | done 57 | } 58 | 59 | cd $1 60 | create_csv ${@:2:$#} 61 | cd - 62 | 63 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Rewind: git history analysis scripts 2 | 3 | A small library of handy git analysis scripts 4 | roughly inspired by Gary Bernhardt's [Destroy All 5 | Software](https://www.destroyallsoftware.com/) screencasts, and 6 | extensively documented in my ebook _[Unfuck A Monorail For Great 7 | Justice](http://gilesbowkett.blogspot.com/2013/03/new-ebook-unfuck-monorail-for-great.html)_. 8 | 9 | Use these when you want to quickly generate meaningful history reports 10 | for git projects. 11 | 12 | ## stats 13 | 14 | Run a statistics bash script against a git repository. For each 15 | filename, discover lines of code, number of commits, and the date of the 16 | first and the last commit. Then pull that into a spreadsheet app, sort 17 | by each metric, and get an idea which files are the most important files 18 | in the history of the project. 19 | 20 | bash file_stats.sh [dir] [filetypes] > stats.csv 21 | open -a Numbers stats.csv 22 | 23 | For example: 24 | 25 | bash file_stats.sh /ember-project "js" "handlebars" 26 | 27 | Or: 28 | 29 | bash file_stats.sh /rails-project "rb" "haml" "coffee" "scss" 30 | 31 | ## authorship 32 | 33 | This is more tricky, and requires Ruby. First run the 34 | `determine_authors.sh` script to get a count of all the authorship 35 | events in the repo; then run the `tally_authors.rb` Ruby script to sort 36 | the authorship events by name. 37 | 38 | You can do all that as a one-liner in bash like this: 39 | 40 | ruby tally_authors.rb <(cd /project && /this_dir/determine_authors.sh "js") 41 | 42 | The code isn't tricky at all, and the output is easy to understand, but 43 | it's also easy to misconstrue. That's the tricky part. A developer 44 | could show up with few authorship events because they're new to a team, 45 | because they often do pair programming, because they prefer large 46 | commits to small ones, or for several other possible reasons. Exercise 47 | good judgement. 48 | 49 | Also, where `file_stats.sh` will examine multiple file types, 50 | `determine_authors.sh` and `tally_authors.rb` assumes you're only 51 | working with one type of file at a time. Pull requests welcome! 52 | 53 | Caveat: Running this code against extremely large projects with very 54 | long histories (e.g. Rails) might be very slow. 55 | 56 | ## Why Rewind? 57 | 58 | 59 | 60 | [Rewind is currently a key character in the IDW _Transformers_ 61 | comics](http://tfwiki.net/wiki/Rewind_\(G1\)#IDW_Generation_1_continuity). 62 | 63 | ## license 64 | 65 | MIT license. 66 | 67 | --------------------------------------------------------------------------------