Git blame for file trees
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-whoSee the section on Docker below.
This method requires that you have Go installed.
go install github.com/sinclairtarget/git-who@latestBuilding 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 [email protected]: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:
…You can specify a path to filter the results to only commits that touched files under the given path:
…You can also specify a branch name, tag name, or any "commit-ish" to filter the results to commits reachable from the specified commit:
…Revision ranges also work. This shows the commits made after the release of 3.10.9 up to the release of 3.11.9:
…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 -- fooThe -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:
…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:
…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:
…(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 Dartiailh (1)
├── asdl_c.py...........Batuhan Taskaya (4)
├── myreadline.c........Victor Stinner (1)
├── parser.c
├── pegen.c
├── pegen.h
├── pegen_errors.c......Miss Islington (bot) (8)
└── string_parser.c.....Miss Islington (bot) (4)If a file isn't edited in any of the commits specified by the revision range,
then it won't appear in the output of git who tree, even if the file is in
the working tree. This can make git who tree useful for visualizing the
changes introduced by a branch.
The tree subcommand, like the table subcommand, supports the -l, -f,
-m, and -c flags.
The -l flag will annotate each file tree node with the
author who has added or removed the most lines at that path:
…The -f flag will pick authors based on number of files edited. The -m flag
will pick an author based on last modification time while the -c flag picks
the author who first edited a file.
You can limit the depth of the tree printed by using the -d flag. The depth
is measured from the current working directory.
The -a flag has already been mentioned.
Run git who tree --help to see all options available for the tree subcommand.
hist SubcommandThe hist subcommand prints out a little bar chart / timeline of commit
activity showing the history of contributions to the repository.
…(Git was only released in 2005, so clearly there has been some version control metadata imported from another tool!)
The timeline shows the author who made the most commits in each year. The bar
in the bar chart shows their contributions as a proportion of the total
contributions made in that year. (The # symbol shows the proportion
of total commits by the "winning" author for that year.)
Like with the other subcommands, you can filter the commits examined to just those editing files under a given path:
…The printed timeline will begin with the date of the first commit modifying that path.
You can also filter using a commit-ish. This shows the timeline of contributions since Python's 3.12 release.
…The hist subcommand supports the -l and -f flags but not the -m or -c
flags:
…Run git who hist --help for a full listing of the options supported by the
hist subcommand.
All of the git who subcommands take these additional options that further
filter the commits that get counted.
The --author and --nauthor options allow you to specify authors to include
or exclude. Both options can be specified multiple times to include or exclude
multiple authors.
The --since and --until options allow you to filter out commits before or
after a certain date respectively. These options each take a string that gets
passed to git log to be interpreted. git log can handle some surprising
inputs. See git-commit(1) for a description of what is possible.
The following example shows the paths edited by Guido van Rossum over the last eight months:
~/repos/cpython$ git who tree -d 1 --since "nine months ago" --author "Guido van Rossum"
./..................Guido van Rossum (11)
├── .github/........Guido van Rossum (2)
├── Doc/............Guido van Rossum (3)
├── Include/........Guido van Rossum (3)
├── Lib/............Guido van Rossum (1)
├── Modules/........Guido van Rossum (1)
├── Objects/........Guido van Rossum (1)
├── PCbuild/........Guido van Rossum (2)
├── Programs/.......Guido van Rossum (1)
├── Python/.........Guido van Rossum (4)
├── Tools/..........Guido van Rossum (1)
├── configure
└── configure.acIf you want to filter out all commits under a certain path or using a certain extension (i.e. ignore these files), you can do so using Git's "exclude" pathspec magic.
The following example counts all files under the Parser/ directory but
excludes .c files:
~/clones/cpython$ git who tree -- Parser ':!*.c'
Parser/...................Benjamin Peterson (56)
├── lexer/................Pablo Galindo Salgado (1)
│ ├── buffer.h..........Lysandros Nikolaou (1)
│ ├── lexer.h...........Lysandros Nikolaou (1)
│ └── state.h
├── tokenizer/............Lysandros Nikolaou (1)
│ ├── helpers.h
│ └── tokenizer.h
├── Python.asdl
├── asdl.py
├── asdl_c.py
├── pegen.h...............Pablo Galindo Salgado (14)
└── string_parser.h.......Pablo Galindo Salgado (1)Git supports other kinds of pathspec magic but the "exclude" pathspec magic is
the only one supported by git who.
git who caches data on a per-repository basis under XDG_CACHE_HOME (this is
~/.cache if the environment variable is not set).
You can disable caching by setting GIT_WHO_DISABLE_CACHE=1.
If you install the git-who binary somewhere in your path, running git who
will automatically invoke it with no further configuration. This is a Git
feature.
If you install the binary using a different name or just like to be explicit you can configure a Git alias in your global Git config like so:
[alias]
who = "!git-who-executable-name"See here for more information about Git aliases.
Quite often, people end up committing to a repository under different names or using different email addresses. For example, someone might make a commit using the name "Nathan Smith" and their work email address and then later make a commit using the name "Nate Smith" and their personal email address.
How can you make sure that all of someone's commits are counted together instead of being attributed to three or four different people with slightly different names?
Git already has a solution for this problem called Git
mailmap. If a .mailmap file is present
in a Git repository, git who will respect it.
If you have a .git-blame-ignore-revs file at the root of your repository,
git who will skip all commits named in that file. The format of the file
should be identical to the format of the file expected by the
--ignore-revs-file option of git blame.
Note that git who will not consult the value of blame.ignoreRevsFile in
your Git configuration. If there is a file named .git-blame-ignore-revs at
the root of your repository, git who will use it. Otherwise no commits will
be skipped.
You can run git-who as a Docker container without installing it on your
system directly. Follow these steps to build and use the Docker image.
To build the git-who Docker image, run the following command from
No open issues yet, or sync has not completed.