git-who is a command-line tool for answering that eternal question:
Who wrote this code?!
Unlike git blame, which can tell you who wrote a line of code, git-who
tells you the people responsible for entire components or subsystems in a
codebase. You can think of git-who sort of like git blame but for file
trees rather than individual files.
This README contains comprehensive documentation. For an overview, see Who Will Maintain Vim? A Demo of Git Who.
See releases.
$ brew install git-who
See the section on Docker below.
This method requires that you have Go installed.
go install github.com/sinclairtarget/git-who@latest
Building from source requires that you have Go, Ruby, and the rake Ruby gem
installed. Note that these are only required when building from source; you
can download and run one of the binary releases without installing any of these
tools.
$ git clone git@github.com:sinclairtarget/git-who.git
$ cd git-who
$ rake
$ ./git-who --version
(In the following examples, git-who is invoked as git who. This will work
automatically as long as Git can find git-who in your PATH. See the Git
Alias section for more details.)
git who has three subcommands. Each subcommand gives you a different view of
authorship in your Git repository.
table SubcommandThe table subcommand is the default subcommand. You can invoke it explicitly
as git who table or implicitly just as git who.
The table subcommand prints a table summarizing the contributions of every
author who has made commits in the repository:
~/clones/cpython$ git who
┌─────────────────────────────────────────────────────┐
│Author Last Edit Commits│
├─────────────────────────────────────────────────────┤
│Guido van Rossum 2 mon. ago 11,213│
│Victor Stinner 1 week ago 7,193│
│Fred Drake 13 yr. ago 5,465│
│Georg Brandl 1 year ago 5,294│
│Benjamin Peterson 4 mon. ago 4,724│
│Raymond Hettinger 1 month ago 4,235│
│Serhiy Storchaka 3 days ago 3,366│
│Antoine Pitrou 10 mon. ago 3,180│
│Jack Jansen 18 yr. ago 2,978│
│Martin v. Löwis 9 yr. ago 2,690│
│...3,026 more... │
└─────────────────────────────────────────────────────┘
You can specify a path to filter the results to only commits that touched files under the given path:
~/repos/cpython$ git who Tools/
┌─────────────────────────────────────────────────────┐
│Author Last Edit Commits│
├─────────────────────────────────────────────────────┤
│Guido van Rossum 8 mon. ago 820│
│Barry Warsaw 1 year ago 279│
│Martin v. Löwis 9 yr. ago 242│
│Victor Stinner 1 month ago 235│
│Steve Dower 1 month ago 228│
│Jeremy Hylton 19 yr. ago 178│
│Mark Shannon 4 hr. ago 131│
│Serhiy Storchaka 2 mon. ago 118│
│Erlend E. Aasland 1 week ago 117│
│Christian Heimes 2 yr. ago 114│
│...267 more... │
└─────────────────────────────────────────────────────┘
You can also specify a branch name, tag name, or any "commit-ish" to filter the results to commits reachable from the specified commit:
~/clones/cpython$ git who v3.7.1
┌─────────────────────────────────────────────────────┐
│Author Last Edit Commits│
├─────────────────────────────────────────────────────┤
│Guido van Rossum 6 yr. ago 10,986│
│Fred Drake 13 yr. ago 5,465│
│Georg Brandl 8 yr. ago 5,291│
│Benjamin Peterson 6 yr. ago 4,599│
│Victor Stinner 6 yr. ago 4,462│
│Raymond Hettinger 6 yr. ago 3,667│
│Antoine Pitrou 6 yr. ago 3,149│
│Jack Jansen 18 yr. ago 2,978│
│Martin v. Löwis 9 yr. ago 2,690│
│Tim Peters 10 yr. ago 2,489│
│...550 more... │
└─────────────────────────────────────────────────────┘
Revision ranges also work. This shows the commits made after the release of 3.10.9 up to the release of 3.11.9:
~/clones/cpython$ git who v3.10.9..v3.11.9
┌─────────────────────────────────────────────────────┐
│Author Last Edit Commits│
├─────────────────────────────────────────────────────┤
│Miss Islington (bot) 9 mon. ago 2,551│
│Victor Stinner 9 mon. ago 367│
│Serhiy Storchaka 9 mon. ago 304│
│Erlend Egeberg Aasland 2 yr. ago 202│
│Christian Heimes 2 yr. ago 200│
│Mark Shannon 1 year ago 157│
│Irit Katriel 10 mon. ago 135│
│Nikita Sobolev 10 mon. ago 126│
│Pablo Galindo Salgado 1 year ago 117│
│Pablo Galindo 9 mon. ago 97│
│...574 more... │
└─────────────────────────────────────────────────────┘
Just like with git itself, when there is ambiguity between a path name
and a commit-ish, you can use -- to clarify the distinction. The
following command will show you contributions to the file or directory
called foo even if there is also a branch called foo in your repository (or
even if the file/directory was previously committed but has since been deleted):
$ git who -- foo
The -m, -c, -l, and -f flags allow you to sort the table by different
metrics.
The -m flag sorts the table by the "Last Edit" column, showing who
edited the repository most recently. The -c flag sorts the table by first
edit, so that the authors who committed to the repository earliest are at the
top.
The -l flag sorts the table by number of lines modified, adding some more
columns:
$ git who -l
┌──────────────────────────────────────────────────────────────────────────────┐
│Author Last Edit Commits Files Lines (+/-)│
├──────────────────────────────────────────────────────────────────────────────┤
│Guido van Rossum 2 mon. ago 11,213 14,135 1.3m / 793,252│
│Antoine Pitrou 10 mon. ago 3,180 3,868 944,685 / 776,587│
│Jack Jansen 18 yr. ago 2,978 5,887 836,527 / 691,078│
│Benjamin Peterson 4 mon. ago 4,724 6,957 690,740 / 781,700│
│Georg Brandl 1 year ago 5,294 9,139 644,620 / 640,217│
│Martin v. Löwis 9 yr. ago 2,690 4,557 570,632 / 389,794│
│Victor Stinner 1 week ago 7,193 11,382 464,474 / 460,396│
│Brett Cannon 1 month ago 2,022 2,841 305,631 / 283,178│
│Serhiy Storchaka 3 days ago 3,366 9,955 335,209 / 208,899│
│Christian Heimes 1 year ago 1,553 4,191 339,706 / 178,947│
│...3,022 more... │
└──────────────────────────────────────────────────────────────────────────────┘
The -f flag sorts the table by the number of files modified.
There is also an -n option can be used to print more rows. Passing -n 0
prints all rows.
Run git-who table --help to see additional options for the table subcommand.
tree SubcommandThe tree subcommand prints out a file tree showing files in the working tree
just like tree. Each node in the
file tree is annotated with information showing which author contributed the most
to files at or under that path.
Here is an example showing contributions to the Python parser. By default, contributions will be measured by number of commits:
~/repos/cpython$ git who tree Parser/
Parser/.........................Guido van Rossum (182)
├── lexer/......................Pablo Galindo Salgado (5)
│ ├── buffer.c................Lysandros Nikolaou (1)
│ ├── buffer.h................Lysandros Nikolaou (1)
│ ├── lexer.c
│ ├── lexer.h.................Lysandros Nikolaou (1)
│ ├── state.c
│ └── state.h
├── tokenizer/..................Filipe Laíns (1)
│ ├── file_tokenizer.c
│ ├── helpers.c...............Lysandros Nikolaou (1)
│ ├── helpers.h...............Lysandros Nikolaou (1)
│ ├── readline_tokenizer.c....Lysandros Nikolaou (1)
│ ├── string_tokenizer.c......Lysandros Nikolaou (1)
│ ├── tokenizer.h.............Lysandros Nikolaou (1)
│ └── utf8_tokenizer.c........Lysandros Nikolaou (1)
├── Python.asdl.................Benjamin Peterson (14)
├── action_helpers.c............Pablo Galindo Salgado (6)
├── asdl.py.....................Benjamin Peterson (7)
├── asdl_c.py...................Benjamin Peterson (42)
├── myreadline.c
├── parser.c....................Pablo Galindo Salgado (34)
├── peg_api.c...................Lysandros Nikolaou (2)
├── pegen.c.....................Pablo Galindo (33)
├── pegen.h.....................Pablo Galindo Salgado (13)
├── pegen_errors.c..............Pablo Galindo Salgado (16)
├── string_parser.c.............Victor Stinner (10)
├── string_parser.h.............Pablo Galindo Salgado (1)
└── token.c.....................Pablo Galindo Salgado (2)
You may notice that some files, like lexer.c, are not annotated.
If a file is not annotated, that is because the author who has
most contributed to that file is the same as the author who
has most contributed to the directory containing the file. This is
done to minimize visual noise.
You can force git-who tree to annotate every file using the -a
flag (for "all"). This flag also prints all file paths that
were discovered while walking the commit history, including those no
longer in the working tree:
~/repos/cpython$ git who tree -a Parser/
Parser/.........................Guido van Rossum (182)
├── lexer/......................Pablo Galindo Salgado (5)
│ ├── buffer.c................Lysandros Nikolaou (1)
│ ├── buffer.h................Lysandros Nikolaou (1)
│ ├── lexer.c.................Pablo Galindo Salgado (4)
│ ├── lexer.h.................Lysandros Nikolaou (1)
│ ├── state.c.................Pablo Galindo Salgado (2)
│ └── state.h.................Pablo Galindo Salgado (1)
├── pegen/......................Pablo Galindo (30)
│ ├── parse.c.................Pablo Galindo (16)
│ ├── parse_string.c..........Pablo Galindo (7)
│ ├── parse_string.h..........Pablo Galindo (2)
│ ├── peg_api.c...............Pablo Galindo (3)
│ ├── pegen.c.................Pablo Galindo (17)
│ └── pegen.h.................Pablo Galindo (9)
├── pgen/.......................Pablo Galindo (8)
│ ├── __init__.py.............Pablo Galindo (2)
│ ├── __main__.py.............Pablo Galindo (5)
│ ├── automata.py.............Pablo Galindo (4)
│ ├── grammar.py..............Pablo Galindo (5)
│ ├── keywordgen.py...........Pablo Galindo (3)
│ ├── metaparser.py...........Pablo Galindo (2)
│ ├── pgen.py.................Pablo Galindo (5)
│ └── token.py................Pablo Galindo (4)
├── tokenizer/..................Filipe Laíns (1)
│ ├── file_tokenizer.c........Filipe Laíns (1)
│ ├── helpers.c...............Lysandros Nikolaou (1)
│ ├── helpers.h...............Lysandros Nikolaou (1)
│ ├── readline_tokenizer.c....Lysandros Nikolaou (1)
│ ├── string_tokenizer.c......Lysandros Nikolaou (1)
│ ├── tokenizer.h.............Lysandros Nikolaou (1)
│ └── utf8_tokenizer.c........Lysandros Nikolaou (1)
├── .cvsignore..................Martin v. Löwis (1)
├── Makefile.in.................Guido van Rossum (10)
├── Python.asdl.................Benjamin Peterson (14)
├── acceler.c...................Guido van Rossum (17)
├── action_helpers.c............Pablo Galindo Salgado (6)
├── asdl.py.....................Benjamin Peterson (7)
├── asdl_c.py...................Benjamin Peterson (42)
├── assert.h....................Guido van Rossum (11)
├── bitset.c....................Guido van Rossum (12)
├── firstsets.c.................Guido van Rossum (13)
├── grammar.c...................Guido van Rossum (20)
...
(The above output continues but has been elided for the purposes of this README.)
Note that, whether or not the -a flag is used, commits that
edited files not in the working tree will still count toward the total
displayed next to ancestor directories of that file. In the above two examples,
Guido van Rossum is shown as the overall highest committer to the Parser/
directory, though it takes listing the entire tree with the -a flag to see
that most of his commits were to files that have since been moved or deleted.
Like with the table subcommand, you can specify a "commit-ish". This
next example shows changes to the Parser/ directory that happened
after the 3.10.9 release up to the 3.11.9 release.
```
~/clones/cpython$ git who tree v3.10.9..v3.11.9 -- Parser/
Parser/.................Pablo Galindo Salgado (52)
├── Python.asdl.........Batuhan Taskaya (1)
├── action_helpers.c....Matthieu D
$ claude mcp add git-who \
-- python -m otcore.mcp_server <graph>