Finding bugs with git bisect

When a bug has crept in to your code that you're having trouble fixing, it helps to know when it was introduced. If you can prove which code caused the bug you can normally work out why. The git bisect command is here to help you track it down...

It works like this. You tell git three things:

  1. The SHA of the most recent good commit.
  2. The SHA of a bad commit that you know has the bug.
  3. A command that git can run that will exit with a non-zero exit status when the bug is present (in other words, you tell it how to run your tests).

Setting up the bisect

Early today I managed to introduce a bug somewhere in my topic branch, while doing some copy writing and tidying up my sales pages. I hadn't edited any real code, but my tests were failing anyway. I had no idea why.

I quickly ran the tests on the master branch to prove that it wasn't a problem with my environment. They passed – the problem was definitely in one of the commits on my topic branch.

I knew that my last good commit was master. The earliest bad commit that I'm aware of is the head of my topic branch.

This is how you pass that information to git:

$ git bisect start [<bad> [<good>]]

So in my case, I typed:

$ git bisect topic-branch master

Testing all the commits

To kick things off you give git the name of a command to run. It needs to exit with an exit status of 0 if the commit is good, and non-zero if it's a bad one.

$ git bisect run <command>

My command was a simple one; I just ran some of my tests.

$ git bisect run ruby test/functional/activities_controller_test.rb

My computer churned away for a bit, continually re-running the tests on different commits. Rather than just running the tests against each commit on the branch, it's smart enough to optimise the full run by selecting which commit to test next with a binary chop.

When it finished I checked the output of git bisect log to see the results. Look towards the bottom of the output and find the last commit marked as bad. In my case, it looked like this:

# bad: [61a0e9249a2ef2b1a1cb31562782bf2de05c782c] Added pricing page with the Early Adopter plan.

Adding a new page seemed pretty innocuous, but finding the problem was pretty straightforward when I looked at the diff. An innocent change to the routes file had uncovered a badly written (unrelated) test that started failing as a side effect.

Cleaning up

When you know where the problem came from, a quick reset will get you back to the head of your branch:

$ git bisect reset

I love feedback and questions — please feel free to get in touch on Mastodon or Twitter, or leave a comment.

Published on in Git