Why Git blame sucks for understanding WTF code (and what to use instead)
You’re happily working your way through a codebase when you happen upon some code that makes you stop and think: What the…!? Maybe it’s a method that’s doing something surprising. Or perhaps it’s doing something completely unsurprising, it’s just doing it in a surprising way.
Git blame to the rescue?
When this happens you might instinctively reach for git blame
to help you figure out what’s going on. After all, git blame
gives you the commit that most recently touched the line, and often that’s enough to point you in the right direction. But often it isn’t and you’re none the wiser. That’s because:
- git blame is too coarse: it reports against the whole line. If the most recent change isn’t related to the part of the line you’re interested, you’re out of luck.
- git blame is too shallow: it only reports a single change; the most recent one. The story of the particular piece of code you’re interested in may have evolved over several commits.
- git blame is too narrow: it only considers the file you are running blame against. The code you are interested in may also appear in other files, but to get the relevant commits on those you’ll need to run blame several times.
If you only use git blame
you’re limiting yourself to a one-dimensional perspective of the code you’re trying to understand. Wouldn’t it be better to view things in 3D!?
A more powerful way to trace the history of WTF code
Thankfully Git has some pretty powerful search tools built right in. Let’s take a closer look at some of the tools at our disposal.
Find all commits containing a particular piece code
If git blame
is entry-level history search, git log -S
(also known as “the pickaxe”) is how you take things to the next level. It lets you search for all commits that contain a given string:
$ git log -S "method_name"
This will return all the commits that contain method_name
across the entire repository, giving you a bespoke history of a piece of code across both multiple commits and files. You can use the pickaxe to examine the entire history of a particular snippet: all the places it’s been used; how it’s usage has changed; if it’s moved file; when it was first introduced, and more.
See the diffs alongside the commit messages
If you include the -p
option (short for --patch
) you get the full diff alongside the commit messages, giving you even more context and making it easier to spot the relevant changes:
$ git log -S "method_name" -p
Find the commit that first added some code
Say you want to find the first commit that introduced a particular class, method or snippet of code. You can use the pickaxe combined with the --reverse
option to get the commits in reverse-chronological order so the commit where the code first appears is listed at the top:
$ git log -S "method_name" -p --reverse
Find when a piece of code was deleted
Because the pickaxe search finds both additions and deletions, you can even use it to find code that has since been deleted:
$ git log -S "deleted code" -p
Assuming the snippet no longer exists in the codebase the first commit returned will be the one where it was removed.
Limit the scope of the search
Because we’re using git log
you can also provide a path to focus the search to a given file or folder:
$ git log -S "some code" -p app/models/user.rb
Search deeper with a regular expression
I mostly stick to the pickaxe because I find working with regular expressions cumbersome, but if you want to perform a more advanced search using a regular expression you can do so with -G
instead:
$ git log -G "REGEX HERE"
It’s also worth noting that searching with -S
has a subtle limitation that -G
does not: -S
only shows commits that either added or removed the search string. If the string you are searching for moved within the same file but was otherwise unchanged, -S
won’t include that commit, whereas -G
will.
Search the commit messages themselves
Sometimes searching for literal code is too narrow, or the information you’re after isn’t actually in the code but in the commit messages themselves. In which case you can use --grep
to search commit messages instead:
$ git log --grep "commit message search"
In summary
Next time some code has you puzzled and you want to understand more about it, look beyond git blame
and dig deeper into the history using the pickaxe and friends:
- Find the entire history of a snippet of code with
git log -S
- Include
-p
to see the diff as well as the commit messages - Include
--reverse
to see the commit that introduced the code listed first - Scope search to specific folders or files by including a path
- Search with a regular expression using
git log -G
- Search commit messages using
git log --grep
Epilogue
Of course searching a codebase’s history will only be fruitful if the history itself is helpful. If the history hasn’t been constructed with atomic commits that have useful commit messages, searching it will likely be a frustrating and unhelpful experience.
To learn more about the how and why of putting together more useful histories, check out A Branch in Time (a talk about revision histories).