james mckay dot net
because there are few things that are less logical than business logic

Finding bugs in your code quickly using git bisect

git bisect is one of my favourite features of Git. It is a binary search tool that lets you quickly track down the revision that introduced a bug. Surprisingly, it doesn’t seem to be all that well known, so I thought it would be worth writing a refresher on what it is and how to use it.

git bisect: an introduction

The idea is very simple. If you know that your latest revision has a bug that wasn’t there a few weeks ago, and you can find a “known good” revision from round about that time, you can conduct a binary search of the revisions in between to find out which one introduced it.

So let’s say that you have 500 revisions to start off with. You’d mark the latest one as bad, then test, say, the 100th revision, find that it works as expected, and mark that as your last known good revision. Git will then automatically update to the 300th revision (halfway in between) for you to test. Mark as good or bad as appropriate, lather, rinse and repeat until you’re done.

Each test halves the range of revisions left to be tested, quickly narrowing the gap. In total, you have to test just \mathcal{O}(\log_2 n) revisions. This means that 1,000 revisions would only take one more test than 500, and one million would only take one more test than 500,000 and ten more tests than a thousand. Once you’ve found the offending change, you can very easily zoom right in on the problematic lines of code, rather than having to spend ages stepping through it all in the debugger.

How to use it

Before you start your bisect session, save your work using git commit or git stash. Then to start off your bisect session, type:

$ git bisect start

Next you need to tell Git the range to start off with. If your current HEAD revision is the bad one, you can just mark it as bad as follows:

$ git bisect bad

Next check out a revision that you know to be good and tell Git that it is a good one:

$ git checkout KNOWN_GOOD_REVISION
$ git bisect good

Git will now move to a revision halfway in between the two, choosing the next revision for us to test. You will see something like this:

Bisecting: 31 revisions left to test after this (roughly 5 steps)
[89f7bc018b5fc34c01bea545e3641ee2c77241ac] Bump version

Recompile and re-test your code at this revision. Look for the specific bug that you are trying to track down (ignore any other bugs for the time being) and mark as either bad or good as required:

$ git bisect bad
$ git bisect good

After each of these steps, Git will choose another revision halfway in between, until you end up with the revision that introduced the bug:

$ git bisect bad
164f5061d3f54ab5cba9d5d14ac04c71d4690a71 is the first bad commit
commit 164f5061d3f54ab5cba9d5d14ac04c71d4690a71
Author: James McKay <code@jamesmckay.net>
Date:   Sun Nov 11 14:18:44 2018 +0000

    Move some test fixtures about for consistency.

:040000 040000 d8dc665d03d1e9b37c5ee2dcde8acc032e306de8 0077c62618b69a20e5dbf6a61b42701a3ba2c156 Msrc

Once you’ve found the offending commit, reset to go back to where you started:

$ git bisect reset

Some useful tips

Use git bisect log to see a list of all the revisions you’ve checked so far:

$ git bisect log
git bisect start
# bad: [e38970b3100deecfdbc0ec183c527b49a6e68157] Don't auto-register types by default. Resolves #27.
git bisect bad e38970b3100deecfdbc0ec183c527b49a6e68157
# good: [dcb6a346e9130e736f45f65761ee57fd337483d7] Bit of tidying up.
git bisect good dcb6a346e9130e736f45f65761ee57fd337483d7
# good: [89f7bc018b5fc34c01bea545e3641ee2c77241ac] Bump version
git bisect good 89f7bc018b5fc34c01bea545e3641ee2c77241ac
# bad: [c08ed22ef9ac9cc66c56562b01143333fd61beae] Builders for conventions by name and by scan.
git bisect bad c08ed22ef9ac9cc66c56562b01143333fd61beae
# bad: [3fbc17dc37c35f963c5cea22814408ceac61787f] Bump version: release 0.2.0.
git bisect bad 3fbc17dc37c35f963c5cea22814408ceac61787f
# good: [e60f5d82b16e7b6ae739fa21cb1fc6c224d11c1a] Add link to documentation
git bisect good e60f5d82b16e7b6ae739fa21cb1fc6c224d11c1a
# good: [052e765169b71e691c70b7f458593f5552c75d41] Add resolution for arrays.
git bisect good 052e765169b71e691c70b7f458593f5552c75d41
# bad: [164f5061d3f54ab5cba9d5d14ac04c71d4690a71] Move some test fixtures about for consistency.
git bisect bad 164f5061d3f54ab5cba9d5d14ac04c71d4690a71
# first bad commit: [164f5061d3f54ab5cba9d5d14ac04c71d4690a71] Move some test fixtures about for consistency.

Use git bisect visualize to show your bisect progress in a GUI tool:

$ git bisect visualize

If you can’t tell whether a revision is bad or good (for example, because it won’t compile), use git bisect skip:

$ git bisect skip

On a final note, you don’t need to worry if you haven’t been meticulous about using git rebase to keep your source history linear. git bisect is smart enough to handle branches.

All in all, git bisect is a really useful tool. It allows you to zoom in on bugs in your source code very quickly even in large repositories with extensive histories. Using it is a skill that I would heartily recommend for every developer and tester’s toolbox.