Jacob Padilla

Efficiently Debug Your Code With Git Bisect

Scenario

Have you ever been coding along, only to realize there's a bug in your code, and you can't determine what's causing it!? Figuring out exactly what the issue is can be a huge pain. If only there were a way to quickly find the commit where the problem started... Then, you could look at the code you committed and see exactly what's causing the bug!

Solution

This is what Git bisect is for! At its core, Git bisect is a tool for performing binary searches across your project's commit history. The goal is to pinpoint the exact commit where things went wrong, which is incredibly useful in large projects with extensive commit histories.

Let's use this diagram as an example - we have our commit history, with the oldest commit on the left and the newest commit (with the bug) on the right. All of the commits in red contain the error, but in real life, we wouldn't know which commit this is.


Git bisect example diagram

First we need to start Git bisect via the following command:

$ git bisect start

After initiating Git bisect, our next step is to specify two commits: one where the error is present (often the most recent commit) and another earlier commit without the error. Generally, when I'm using Git bisect, I pick a somewhat random, old commit that I'm positive won't contain the error I'm searching for. In the diagram above, I set the “bad” commit to the first one and the “good” commit to the second-to-oldest commit by using the following commands:

Commit with the error:

$ git bisect bad [commit_hash_of_error_commit]

Commit without the error:

$ git bisect good [commit_hash_of_good_commit]

To find these commit hashes, I'd recommend using the git log -graph -all command. If you have a project with an extensive commit history and want to avoid arbitrarily selecting a “good” commit, I'd recommend using the git checkout [commit_hash] command. This allows you to quickly view old commits and easily decide which one you want to mark as “good”; you can read more about this in another article of mine: Time Travel with Git: How to View, Undo, and Go Back To Commits.

Once you specify a good and bad commit, the binary search begins. Git bisect will move the HEAD, which in Git is a pointer to the currently checked-out commit, to specific points in your commit history for testing. As the HEAD moves to each of these commits, your working directory's files are updated to reflect their state at that particular commit. This allows you to examine the code, run tests, and determine if the error existed in your codebase at different points in time.

If the commit contains the error, you can mark it as bad via the following command:

$ git bisect bad

But if it's an error-free commit, you can mark it as good:

$ git bisect good

Once you've found the two commits where the first one is error-free and the second one starts the error, you can stop the Git bisect program and go back to the original HEAD commit by typing in the following command:

$ git bisect reset

And now you know what's causing your bug!

Run Git Bisect With an Automated Test Script

A cool feature of Git bisect is being able to write scripts to let it automatically find the starting point of a bug! To do this, first create a script that tests for the bug's presence. If the bug is present, the script should exit with a non-zero status (In Python, you can do this by raising an exception), but if the commit is all-clear, exit with a status of 0 (indicating a ‘good’ condition).

Your script might look something like this:

# ... your testing code ...

if bug_condition:
    raise Exception("Bug found")

# ... rest of your testing code …

In this script, bug_condition represents the logic that checks for the bug. If the bug is present, the script raises an exception, causing Python to exit with a non-zero status, which is interpreted by Git bisect as a ‘bad’ commit.

Now, we just run the following command and Git bisect will handle the rest:

$ git bisect run python3 test_script.py

With this command, Git bisect will cycle through your commits, automatically running test_script.py at each stop. You still need to start Git bisect and set a good and bad commit boundary, but after that, you can use an automated script to run the binary search and find the commit that started the error!

No matter which way you decide to use Git bisect, it's an incredibly handy tool to have in your arsenal and makes finding the starting point of bugs much easier!