git bisect is your time machine for finding regressions, but it’s not about finding when a bug appeared; it’s about finding the commit that introduced it.

Let’s say you have a feature that worked perfectly in commit a1b2c3d but is now broken in your current HEAD. You want to pinpoint the single commit that caused this breakage.

# Start the bisecting process
git bisect start

# Tell Git the current state is bad
git bisect bad HEAD

# Tell Git a known good state
git bisect good a1b2c3d

Git will now check out a commit roughly halfway between your good and bad points. Your job is to test this commit.

# Example: Compile your code. If it works:
git bisect good

# Example: Run your test suite. If it fails:
git bisect bad

Git will repeat this process, narrowing down the range of potential commits by half each time. It’s a binary search on your commit history.

Eventually, git bisect will isolate the single commit that introduced the bug. It will print the commit hash and its message.

<commit-hash> is the first bad commit
<commit-message>

To clean up and return to your original HEAD and state:

git bisect reset

Common Pitfalls and Advanced Usage:

  • Non-linear History (Merges): If your history isn’t a straight line, git bisect might check out a merge commit. If that merge commit itself isn’t directly testable (e.g., it doesn’t compile on its own), you can mark it as skip. git bisect skip tells Git to ignore this commit and try a different one.
  • Automating the Test: If you have a script that can automatically determine if a commit is "good" or "bad" (e.g., a test suite that exits with 0 for good, non-zero for bad), you can automate the entire process.
    git bisect run <your-test-script.sh>
    
    This is incredibly powerful for large histories or complex build/test steps.
  • Skipping Commits with git bisect skip: Sometimes, a commit might be unbuildable due to unrelated issues (e.g., a temporary dependency failure). You can tell git bisect to ignore that commit and try another one.
    # After git bisect checks out a commit, if it's broken for reasons
    # unrelated to the bug you're hunting, or simply doesn't build:
    git bisect skip
    
    Git will then pick another commit from the remaining range.
  • Bisecting a Specific File/Feature: If you know the bug only affects a specific file or a particular feature, you can narrow down the bisect range to commits that touched that file or feature.
    git bisect start -- <file_path>
    # or
    git bisect start -- path/to/your/file.js
    
    This tells git bisect to only consider commits that have modified the specified file(s).
  • Bisecting by Tags: You can also use tags instead of commit hashes.
    git bisect start
    git bisect bad HEAD
    git bisect good v1.2.0
    
  • Bisecting with a Different Metric: git bisect isn’t just for functional bugs. You can use it to find when performance regressions were introduced, or when a certain warning started appearing. The key is having a consistent way to measure "good" vs. "bad." For instance, if a test now takes 10 seconds longer:
    # In your bisect script:
    start_time=$(date +%s)
    # Run your test
    end_time=$(date +%s)
    duration=$((end_time - start_time))
    if [ "$duration" -gt 20 ]; then # If test duration exceeds 20 seconds
      git bisect bad
    else
      git bisect good
    fi
    
    This allows you to hunt for performance regressions as effectively as functional ones.
  • Viewing the Bisect Log: If you get lost or want to see what commits git bisect has already considered, you can use:
    git bisect log
    
    This shows the history of your bisecting decisions.

The core idea is that git bisect leverages the fact that your commit history is a directed acyclic graph (DAG). By repeatedly asking Git to check out a commit in the middle of the remaining range and then telling it whether that commit is "good" or "bad," you’re effectively performing a binary search on the commit history, dramatically reducing the number of commits you need to manually inspect.

Once you’ve found the offending commit, the next step is usually to revert it or fix it and then re-apply the fix to your current branch, potentially using git revert <offending-commit-hash> or cherry-picking the fix from a new branch.

Want structured learning?

Take the full Git course →