Debugging With Git Bisect

Last week, we received a bug report that duplicate records were showing up on a list view in our application. The query responsible for generating the list has been undergoing major changes, so determining which specific change introduced the bug proved difficult. Using automated testing and the git bisect command, we were able to drastically reduce the time it took to discover the commit that caused the bug and ship a quick fix.

Our first step in debugging was to make sure that the bug was actually introduced with the recent enhancements we’ve been working on. We checked out an old commit that pre-dated our recent changes and confirmed everything worked as expected. But diffing the file against the good commit wasn’t helpful — there were too many changes to quickly narrow down which specific one introduced our regression.

Luckily, there is a command built in to git called git bisect that was created specifically for the problem at hand. When using git bisect, you specify a “good” commit and a “bad” commit. It then uses a binary search algorithm to check out commits at different intervals between the good and bad commits until you find the first bad commit where the bug was introduced. You can do this manually:

git bisect start
git bisect good 18fe24f
git bisect bad ad77ef4
# ...the command will automatically start checking out commits in between the good and bad one in the history
# Specify if the current commit is good or bad and the command will move on to the next one
git bisect good
# OR
git bisect bad

This is will help you find the bad commit, but is time consuming and doesn’t protect against future regressions. To save ourselves time and have confidence that the bug won’t appear in the future, we decided to use Rspec to write an automated test verifying duplicates records don’t appear in the list view. Git bisect supports a ‘git bisect run’ command that we can configure to run our test.

The ‘git bisect run’ command checks the exit code of the script it is running. If the script exits with code 0, the revision will be considered good. If it exits with code 1, the revision will be considered bad. This aligns with how Rspec works: passing tests exit with code 0 and failing tests exit with code 1. I recommend that you put the test you want to run in a new file to avoid potential merge conflicts that may come up and need to be resolved when the bisect is running. Here’s how the command works:

git bisect start
git bisect good 18fe24f
git bisect bad ad77ef4
git bisect run rspec spec/list_view_query_spec.rb

The command will run the test for each revision and notify you of the first bad commit:

ad77ef4fd3c5a165f985a44e1f0926baa2aabc43 is the first bad commit.
Although intimidating at first, git bisect is a simple and powerful tool for debugging regressions — especially when utilizing the “run” command by feeding it a test that verifies the intended behavior. It saves you time and effort by automating the process of finding breaking changes. Give it a try next time you find yourself spelunking through git history searching for the commit that introduced a bug!