tekin.co.uk

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.

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).

Want more juicy Git tips like this straight to your inbox?

You'll get an email whenever I have a fresh insight or tip to share. Zero spam, and you can unsubscribe whenever you like with a single click.

More articles on Git

Authored by Published by