How to find buggy code using git bisect

An introduction to a powerful tool you already own

ยท

5 min read

When you find a bug after making many commits, git provides an extremely powerful tool to identify which commit introduced the problem. It is called git bisect and although I've been aware of it for a long time I have only recently used it.

It's an amazing tool, so to share the love this post will detail what it does, how to use it, and my reflections on using it for the first time.

The theory - what is git bisect?

The problem that git bisect solves is that you know a bug was introduced at some point, but don't know which commit is responsible. If you have a way of testing whether or not the bug exists within a specific version of the code, and you know a point at which the bug didn't exist, then git bisect can tell you the exact commit which introduces the bug.

The principle is that it uses a binary search through your commits, and prompts you to re-test your code at appropriate points. This means that git will count the number of commits between your working version of the code and your broken version, and will pick the halfway point. Once you've tested whether or not that version of the code works, it will pick the halfway point of the remaining commits. Before long you will be left with a single commit which must introduce the bug.

At this point, you hopefully have a much smaller amount of code to review while looking for the bug, and debugging should become much easier.

The practice - how do I use git bisect?

git bisect can be used very easily through the git command line. The only downside is that it doesn't give you any instructions about how to use it, so you need to already know what you're doing. Happily, it's very easy and I'm about to tell you everything you need to know.

  1. To start the process, simply run git bisect start. Git doesn't output anything at this point, so it may seem like nothing has happened, but behind the scenes it has started the process of finding your bug for you.

  2. Next, tell git when your code worked by running git bisect good <commit/branch>. You can either use a commit hash, or the name of a branch which doesn't contain the bug.

  3. You also need to tell git when your code is broken. As you might expect, you run git bisect bad <commit/branch>. At this point, git will give you some output. It will tell you how many commits there are which need testing, and roughly how many times you will need to do a test to identify the single commit which introduces the bug.

  4. You can now do your testing. Git has already checked out the best commit to test, so go ahead and do your testing. All you need to know is whether or not this version of the code contains the bug you're investigating.

  5. Once you have tested, you need to tell git the results of your test. Simply run git bisect good if the code works, or git bisect bad if the bug is present. Git will now check out a different commit.

  6. Repeat your testing, and tell git the results again. This is an iterative process, so each time you have finished testing you should run git bisect good or git bisect bad.

  7. Before long, git will have identified the commit which introduces the bug. It will also be checked out for you. Git will tell you the commit hash and display an overview of the commit.

  8. At this point you are in detached head mode. You have a commit checked out, but you are not making changes to a branch. Now that you know which commit is the problem, it is time to get back to the code you started with (so that you can fix the bug). Run git bisect reset and git will check out the branch where you started. At this point, you have finished using git bisect and found the problematic commit - congratulations! ๐ŸŽ‰

If there is some reason you can't test the commit which git has checked out, then instead of running git bisect good or git bisect bad then you can run git bisect skip. Doing this means the process may take slightly longer, and git may be unable to tell you exactly which commit contains the bug, but it means you can use git bisect even if some of your commits are untestable (e.g. if the build is broken for a reason unrelated to the bug you are investigating).

My reflections on using git bisect for the first time

Using git bisect really shows the value of making small commits. If all of your changes are lumped together in one big commit (or have been squashed into one) then it is very difficult to find which change is relevant when looking through source code history.

With small commits, git bisect allowed me to find the problem in my code very quickly. Once I knew which commit to look at, the problem jumped out at me because I only needed to review a small amount of code.

It is a shame that there is not some output from all of the git bisect commands, to reassure you that the process is working, and tell you what the next step is. Having said that, it's not a difficult process to follow once you have had it explained.

Can I just read the commit history myself?

You don't have to use git bisect to look through a large list of commits looking for ones that might be relevant. How easy that would be depends in part on the quality of the commit messages that you/your team write. If the list of commits isn't too big, I would probably read through the commit messages before reaching for git bisect as there is a bit of overhead with git bisect because testing typically takes a bit of time. However, what git bisect gives you is a guarantee that you will find a single commit , and confidence that it will be the right commit, assuming you correctly report the results of your testing.

Conclusion

git bisect is a very useful tool to be aware of. Keep your commits small (which is best practice anyway) and bear git bisect in mind next time you are investigating a bug and want to see what code change introduced it.

ย