The Magic of Git Bisect (And Why Small Commits Are Important!)
Have you ever had a bug slip through CI/CD, and the code you were CERTAIN worked a while back is no longer working as designed?
If the bug is hard to find or in a piece of code that is hard to debug, git bisect
may be the tool you are looking for. If you practice good coding habits like making many, small-sized commits and ensuring every commit is buildable, then git bisect
can find the problem with laser-like ability.
The git bisect
command will perform a binary search with your help to find a commit that introduced a bug. See the documentation for more information. This post is designed to be a primer only, and the deeper knowledge (like how to use replay if you get lost during your bisect, or reassigning the terms ‘bad’ and ‘good’) will be for the reader to figure out.
The best way to learn a tool is to use a tool, so grab this demo repo. If you run this and click on the Contacts menu, you should see an issue with the contact list:
Note: This bug and repo are horribly contrived but will teach the usage of git bisect
and so are sufficient for this exercise. I suspect a “real life” bug will be much more sinister and only visible in certain cases, but I wanted something that would be easy to run and obvious to reproduce; you’ll see why after you’ve done a few steps. Any dev worth their salt would have discovered something of this nature early on (probably as soon as they tested their code).
This is obviously wrong, so let’s use git bisect
to figure out where it went wrong. In your favorite terminal program, navigate to this directory. Type this command:
git bisect start
Since we’re at the tip of master, and the code is broken here, type:
git bisect bad
We’ve now told git that this commit is the first bad one we know about. In your own code, it will be up to you to find a good commit, probably at some release tag or other step further back from where you branched to start your changes. For this demo, take it on faith that 6a47567138eed48d21ab89616049f1c5d6336a90 is a good commit, and check out that commit:
git checkout 6a47567138eed48d21ab89616049f1c5d6336a90
Now, tell git bisect
that this commit is good:
git bisect good
The magic now begins. Git will pick a commit halfway between the two commits and check it out for you. You must only run the program, check for bad or good behavior, and issue the appropriate command to git (git bisect bad
or git bisect good
). Continue this until you find the bad commit. If you accidentally type the wrong command, you can start over by typing git bisect reset
, and then check out master again and start over.
Once git bisect
finds the bad commit, it will keep that commit checked out until you issue the reset command, at which point it will check out the last commit you originally checked out when you started your bisect.
It becomes immediately apparent why two things should always be true with your git hygiene. First, all commits should be as small as possible while still functional. This allows for the laser-like detection of which commit introduced bad behavior. If you check in a monolith of a commit, sure git bisect
can find that commit, but you will have no idea where in the change the bug was introduced. Secondly, all commits should build and run. Otherwise, you have no way of knowing if the commit is good or bad.
If you happen to run into commits that can’t be built and run, it’s not the end of the world. In that case, you issue the command git bisect skip
, and it will go on to the next commit, but at the end of your bisect, you will have a range of commits that could have introduced the bug.