I came across this awesome, lesser known feature in git a while ago. git bisect allows you to efficiently track down the commit that introduced a bug using a binary search.

The Problem

Let’s consider the following scenario. You have the following commit history:

A -> B -> C -> D -> E (HEAD)

Right now, in HEAD, you have a bug, and you have no idea why. But, you’ve been diligent about committing frequently, so you know that if you’re able to track down the commit that introduced the bug, you would be able to easily determine the cause. So, what are your options?

Well, you could checkout each individual commit and check to see if the bug is present. If the bug is present, you check out the prior commit, and so on until the bug is no longer present. At that point, you have identified the first commit with the bug and you can begin working on resolving it.

This would work, but lets say you have a ton of commits and its just not feasible to go through each commit and test for the bug. Well, you could apply your algorithms knowledge and perform a binary search. You could go pick a random commit from a while ago (when you’re sure the bug wasn’t present). Find the halfway point, see if the bug is present. If it is, perform the same action on the set of commits to the left. If not, perform the same action on the set of commits to the right. Repeat until you’ve narrowed it down to the single commit that introduced the bug. You’ve now accomplished this task in 0(log n) worst case complexity. This is a good solution.

So why not automate it?

The Solution

That’s where git bisect helps us out. It simplifies the process of tracking down the first bad commit by walking us through a binary search.

Lets go back to our previous scenario:

A -> B -> C -> D -> E (HEAD)

The bug is present in HEAD (E). E is our known bad commit. We just checked out commit A, and the bug is not present. A is our known good commit. So, we know that the bug was introduced between commit A and E. Let’s try out git bisect.

$ git bisect start
$ git bisect bad E
$ git bisect good A
Bisecting: 1 revision left to test after this (roughly 1 step)
[C] Commit C

We start by running git bisect start. Then, we tell git that commit E is bad, by running git bisect bad E. Alternatively, you could omit the revision and run git bisect bad to set HEAD as a bad commit.

Then, we run git bisect good A, to tell git that A is a good commit. This kicks off the binary search. We move to the halfway point and git automatically checks out C. We can check for the presence of the bug at this point. Lets say that the bug is present. We run…

$ git bisect bad
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[B] Commit B

We’ve told git that C is bad. Now we’ve narrowed the issue down to either C or B. Lets check B

$ git bisect bad
B is the first bad commit
commit B
Author: Andrew Page <andrew>
Date:   Wed Dec 9 12:04:20 2015 -0800
    B

The bug is present in B, so B is the first bad commit. We’ve simplified our binary search process by using git bisect, and we’ve easily tracked down a bad commit. But, there’s a way to make this better…

The Cooler Solution

We’ve come a long way since the first attempt at tracking this bug down, but we can always do better. git bisect offers an awesome way to automate this, provided that you’re able to check for the presence of the bug by running a command that returns a non-zero exit code if the bug is present.

The vast majority of testing tools support this, so if you have a test that can check for the presence of the bug, you’re golden. Back to our scenario, lets say we have an Rspec testing suite that can accurately detect this bug.

$ git checkout A
$ rspec ; echo $?
0
$ git checkout E
$ rspec ; echo $?
1

We’ve verified that rspec will detect our bug and return a non-zero exit code if the bug is present. Let’s automate this, with git bisect run. This tool will run the script you pass it. If the script exits with a 0, its considered a good commit. If not, its considered a bad commit.

$ git bisect start HEAD A
Bisecting: 1 revision left to test after this (roughly 1 step)
[C] Commit C

$ git bisect run test-error.rb
running rspec
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[B] Commit B
running rspec
B is the first bad commit
commit B
Author: Andrew Page <andrew>
Date:   Wed Dec 9 12:03:59 2015 -0800

    Commit B

bisect run success

There we go, in two lines we’ve completely automated the entire binary search! This will allow you to track down bad commits much more easily.