Git blame all the way back

Sometimes I want to know who wrote a line of code. I run git blame somefile.jsand find the line. It has the commit ID, author name, date and line number, and usually that is enough, but there are a few things that can go wrong.

Commit ID is 00000000000

When the commit ID is 00000000000 it means I have changed that line locally. I don’t want to blame myself, at least not my present self. My present self is perfect, my past self is the one who makes mistakes!

The default git blame blames files in the current workspace, which includes my changes that I haven’t committed yet. I can specify a different point in history to run the blame. In this case I want the head of the branch, without my workspace changes:

git blame HEAD -- somefile.js

No more undecuple-zero commit ID! I can see the actual commit where the line was changed. I can use git show <commit-id> to see what was going on when the line changed.

Commit is too recent

But what if I want to go back further? Sometimes the commit is just an indentation change, or something else on the line that is not what I am looking for.

This is when I start going through a set of steps that lets me walk back through the history of the file.

  1. Blame the file and get the commit ID: git blame HEAD -- somefile.js
  2. Show the commit. This is optional, if you want to see the context the change was made in. git show <commit-id>
  3. Blame the parent of that commit: git blame <commit-id>~ -- somefile.js

Here is an example:

$ git blame HEAD -- somefile.js
# output like this:
# 3d788f8d80a (David Mason 2017-08-17 07:58:16 +1000 15) ...
# 3d788f8d80a (David Mason 2017-08-17 07:58:16 +1000 16) ...
$ git show 3d788f8d80a
# I look over the changes and say:
# "Hmm, interesting" (this makes me look smart to anyone nearby).
$ git blame 3d788f8d80a~ -- somefile.js
# output like this:
# 9ec30f4228d (Someone Else 2016-11-01 10:48:22 +1000 15) ...
# 9ec30f4228d (Someone Else 2016-11-01 10:48:22 +1000 16) ...
$ git show 9ec30f4228d
# I look at the changes and exclaim:
# "Aha! I knew Someone Else was to blame!"

fatal: no such path somefile.js in <commit-id>

One problem you may hit is if the file was moved or renamed somewhere along the way. You ask for the current filename at some commit, and git spits back fatal: no such path ... because that filename did not exist back then.

When the file has been renamed, git will show the filename/path in the output of git blame, like this:

$ git blame somefile.js
4df567c5 somefile.js (David Mason 2017-09-27 ...
4df567c5 somefile.js (David Mason 2017-09-27 ...
6bec0339 somefile.js (David Mason 2017-09-27 ...
6bec0339 somefile.js (David Mason 2017-09-27 ...
9b6e7b77 some-other-file.js (David Mason 1990-04-01 ...
9b6e7b77 some-other-file.js (David Mason 1990-04-01 ...
4df567c5 somefile.js (David Mason 2017-09-27 ...

So I can see that some of the lines were added or modified back when the file was called some-other-file.txt. If I want to see older history of those lines I will have to use the other filename in my blame command:

git blame 9b6e7b77~ -- some-other-file.js

This can still fail if some-other-file.js was created at commit 9b6e7b77 or had a different name in the parent commit (9b6e7b77~). You can always git show the commit and look for whether the file was created or renamed:

$ git show 9b6e7b77
commit 9b6e7b77dd94fa7ec7b15b3ca70b897c2d00f816
Author: David Mason <drdmason@gmail.com>
Date: Sun April 1 09:31:24 1990 +1000
Renaming my file to make it seem more modern.
diff --git a/really-old-filename.js b/some-other-file.js
similarity index 100%
rename from really-old-filename.js
rename to some-other-file.js
$ git blame 9b6e7b77~ -- really-old-filename.js

And that’s all I have for now. Happy blaming!

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.