Git Bisect: Debugging with Binary Search

Debugging issues in a large codebase can be challenging, especially when you need to identify which specific commit introduced a bug. If you have hundreds or even thousands of commits in your Git history, manually checking each one to locate the problematic change is time-consuming and error-prone.

Git Bisect: Debugging with Binary Search

Overview

Debugging issues in a large codebase can be challenging, especially when you need to identify which specific commit introduced a bug. If you have hundreds or even thousands of commits in your Git history, manually checking each one to locate the problematic change is time-consuming and error-prone. This is where Git Bisect comes in.

Git Bisect is a powerful tool that uses a binary search algorithm to help you identify the specific commit that introduced a bug. By dividing the range of commits in half at each step, Git Bisect quickly narrows down the potential culprit, saving you a lot of time compared to a manual search.

In this post, we’ll cover:

  1. What Git Bisect is and why it’s useful.
  2. How Git Bisect works: an overview of the binary search process.
  3. A step-by-step guide to using Git Bisect in a real-world scenario.
  4. Best practices for using Git Bisect efficiently.
  5. Advanced features of Git Bisect.

By the end of this post, you’ll understand how to use Git Bisect to quickly identify problematic commits in your repository, making your debugging process faster and more efficient.

1. What is Git Bisect?

Introduction

Git Bisect is a tool in Git that allows you to efficiently find the commit that introduced a bug by performing a binary search through your commit history. Instead of manually checking each commit in sequence, Git Bisect automates the process by cutting the number of candidate commits in half with each step.

The idea behind Git Bisect is simple:

  • You specify a "good" commit (a commit before the bug was introduced).
  • You specify a "bad" commit (a commit where the bug is present).
  • Git Bisect checks out a commit in between and asks you whether that commit is good or bad.
  • Based on your input, Git Bisect narrows down the range of commits until it identifies the exact commit that introduced the bug.

Since Git Bisect uses a binary search algorithm, it dramatically reduces the number of commits you need to check. For example, if you have 100 commits to check, Git Bisect will typically require only about 7 steps (log2(100)) to find the problematic commit.

Why is Git Bisect Useful?

When you're working on a project with a large commit history, bugs can be introduced at any point in time. It might be difficult to pinpoint when the bug was introduced, especially if you’re dealing with a large team or multiple feature branches. Git Bisect is particularly useful in the following situations:

  • Finding the source of a regression: If a feature that was previously working suddenly breaks, Git Bisect helps you identify the exact commit that caused the regression.
  • Isolating bugs in large repositories: In repositories with hundreds or thousands of commits, Git Bisect saves time by reducing the number of commits you need to inspect manually.
  • Debugging code when changes happen across multiple branches: Even if the bug was introduced across several branches, Git Bisect helps track it down by narrowing the search.

2. How Git Bisect Works: The Binary Search Process

Before we dive into how Git Bisect works, let's briefly understand binary search. Binary search is an algorithm used to find a target value within a sorted range by repeatedly dividing the range in half. Each time, the algorithm checks whether the midpoint of the range is greater or smaller than the target. By halving the range at each step, binary search quickly homes in on the target value, reducing the number of checks required compared to linear search.

In the context of Git Bisect, the target value is the commit that introduced the bug, and the "range" is the sequence of commits between a good commit (before the bug) and a bad commit (where the bug is present). At each step, Git Bisect checks the midpoint commit and asks whether the bug is present in that commit. Depending on your answer, it narrows the range of commits and continues the process until the bug-inducing commit is found.

Step-by-Step Explanation of Git Bisect

Let’s walk through the typical steps involved in using Git Bisect:

    • A good commit where the bug does not exist.
    • A bad commit where the bug is present.
  1. Marking the good and bad commits:
    Once Git Bisect is started, you need to tell Git which commit is good and which is bad.
  2. Performing the binary search:
    After you’ve marked the good and bad commits, Git Bisect checks out a commit approximately halfway between the good and bad commits. It will then ask you to test whether the bug is present in this commit.
  3. Repeating the process:
    Based on your input, Git will continue narrowing down the range of commits by performing another binary search. It checks out another commit in the middle of the remaining range and asks you again to mark it as good or bad. This process continues until Git Bisect identifies the exact commit that introduced the bug.

Finishing Git Bisect:
Once Git Bisect finds the commit that introduced the bug, it will tell you the exact commit hash that caused the issue. You can then stop the bisecting process by running:

git bisect reset

This command will return you to the original branch and state before you started the bisect process.

If the bug is not present, you mark it as good:

git bisect good

If the bug is present, you mark it as bad:

git bisect bad

For the good commit, you use:

git bisect good <commit-hash>

Here, you provide the commit hash of a commit that you know predates the introduction of the bug.

For the bad commit, you use:

git bisect bad

This will typically be the commit where you first noticed the bug.

Starting Git Bisect:
You begin by identifying two points in your Git history: You’ll run the following command to initiate Git Bisect:

git bisect start

3. Example of Using Git Bisect in a Real-World Scenario

Let’s go through a real-world example of using Git Bisect to find a bug in a codebase.

Scenario

Imagine you have a project with a Git history of 500 commits. At some point, you notice that a feature, which used to work correctly, is now broken. After some investigation, you determine that the bug was introduced at some point in the last 100 commits, but you're not sure exactly when.

Step-by-Step Process

  1. Testing commits:
    Now, Git Bisect checks out a commit in the middle of the range between the good and bad commits and asks you to test whether the bug is present. After testing, you mark the commit as either good or bad depending on whether the bug is present.
  2. Continue bisecting:
    Git Bisect will continue checking out commits halfway between the current good and bad commits until it narrows down the range to a single commit.
  3. Identify the bad commit:
    Once Git Bisect identifies the commit that introduced the bug, it will output the commit hash and message for that commit. You now know exactly which commit caused the problem.

Resetting:
After identifying the problematic commit, you can return to your original branch by running:

git bisect reset

Mark the good commit:
You know that the feature was working correctly 100 commits ago, so you mark that commit as good:

git bisect good <commit-hash>

Mark the bad commit:
Since the bug is present in the latest commit, you mark this commit as bad:

git bisect bad

Start Git Bisect:
You start Git Bisect by running:

git bisect start

Identify the good and bad commits:
You know that the feature was working fine in a commit 100 revisions ago, so you find the commit hash of that good commit by running:

git log

After identifying the hash, you note it for later use. You also know the bug is present in the latest commit (HEAD), so this will be your bad commit.

4. Best Practices for Using Git Bisect

While Git Bisect is an incredibly powerful tool, there are several best practices you should follow to ensure it works effectively in your team’s workflow:

1. Use Automated Tests (When Possible)

When using Git Bisect, manually testing each commit to see if a bug is present can be time-consuming. If your project has automated tests, you can integrate them with Git Bisect to automatically test each commit. Here’s how:

git bisect run <command>

For example, if your project has a test suite that can be run using npm test, you can run the following command to automatically test each commit during the bisect process:

git bisect run npm test

Git Bisect will automatically mark a commit as good if the tests pass and bad if the tests fail.

2. Ensure Good and Bad Commits Are Clearly Identified

Before starting Git Bisect, it’s important to ensure that you’ve clearly identified both a good commit (where the bug does not exist) and a bad commit (where the bug is present). If you accidentally mark a commit incorrectly, Git Bisect may give you incorrect results.

3. Regularly Perform Bisecting

If you’re regularly developing new features and fixing bugs, consider using Git Bisect as a standard part of your workflow when identifying regressions. By regularly using this tool, you’ll become more familiar with it and be able to identify problematic commits faster.

Conclusion

Git Bisect is a powerful tool that can drastically reduce the time it takes to locate the commit responsible for introducing a bug. By using a binary search algorithm, it narrows down the range of candidate commits quickly, allowing you to focus on fixing the issue instead of manually hunting for the problematic commit.

Whether you’re debugging a small project or working with a large team on a complex codebase, Git Bisect can help you pinpoint the exact moment when a regression or bug was introduced, saving you countless hours of debugging.

Read next

Best Practices for Using Git Hooks in Teams

Git hooks are powerful tools that allow developers to automate tasks and enforce coding standards within a Git repository. When used properly in a team setting, Git hooks can streamline development workflows, improve code quality, and enhance collaboration.