Something’s broken. It worked last week. Somewhere in the 47 commits since then, someone introduced a bug. You could check each commit manually, or you could let git do the work.

git bisect performs a binary search through your commit history to find the exact commit that introduced a problem. Instead of checking 47 commits, you check about 6.

The Basic Workflow

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Start bisecting
git bisect start

# Mark current commit as bad (has the bug)
git bisect bad

# Mark a known good commit (before the bug existed)
git bisect good v1.2.0
# or
git bisect good abc123

# Git checks out a commit halfway between good and bad
# Test it, then tell git the result:
git bisect good  # This commit doesn't have the bug
# or
git bisect bad   # This commit has the bug

# Git narrows down and checks out another commit
# Repeat until git finds the first bad commit

# When done, return to original state
git bisect reset

Example Session

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
$ git bisect start
$ git bisect bad                    # HEAD is broken
$ git bisect good v2.0.0            # v2.0.0 worked fine

Bisecting: 23 revisions left to test after this (roughly 5 steps)
[abc123...] Add caching layer

$ ./run-tests.sh
Tests pass!

$ git bisect good

Bisecting: 11 revisions left to test after this (roughly 4 steps)
[def456...] Refactor auth module

$ ./run-tests.sh
FAIL!

$ git bisect bad

Bisecting: 5 revisions left to test after this (roughly 3 steps)
[ghi789...] Update dependencies

# ... continue until:

abc123def456789 is the first bad commit
commit abc123def456789
Author: Someone <someone@example.com>
Date:   Mon Feb 20 14:30:00 2024

    Fix edge case in login flow
    
    This commit introduced the bug!

$ git bisect reset

Automated Bisecting

If you have a script that can test for the bug:

1
2
3
4
5
6
git bisect start
git bisect bad HEAD
git bisect good v2.0.0

# Run automatically with a test script
git bisect run ./test-for-bug.sh

The script should:

  • Exit 0 if the commit is good
  • Exit 1-127 (except 125) if the commit is bad
  • Exit 125 to skip (can’t test this commit)

Example Test Script

1
2
3
4
5
6
7
8
9
#!/bin/bash
# test-for-bug.sh

# Build the project (skip if build fails)
make || exit 125

# Run the specific test that catches the bug
./run-specific-test.sh
exit $?

Testing for Specific Behavior

1
2
3
4
5
#!/bin/bash
# Check if a specific string appears in output

./my-program | grep -q "expected output"
# Exit 0 (good) if found, 1 (bad) if not

Handling Untestable Commits

Sometimes a commit can’t be tested (broken build, unrelated failure):

1
2
3
4
# Skip this commit
git bisect skip

# Git will try a nearby commit instead

If too many commits need skipping, git might not find the exact culprit but will narrow it down to a range.

Bisecting Across Branches

1
2
3
4
5
6
# Start from current (broken) branch
git bisect start
git bisect bad

# Good commit can be on another branch or tag
git bisect good origin/stable

Viewing Bisect Progress

1
2
3
4
5
6
7
# See the current bisect log
git bisect log

# Visualize the bisect range
git bisect visualize
# or
gitk

Replaying a Bisect

If you need to redo or share a bisect:

1
2
3
4
5
# Save bisect log
git bisect log > bisect.log

# Replay it later
git bisect replay bisect.log

Common Patterns

Finding a Performance Regression

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#!/bin/bash
# test-performance.sh

# Run benchmark
time_ms=$(./benchmark.sh)

# Fail if slower than threshold
if [ "$time_ms" -gt 1000 ]; then
    exit 1  # Bad - too slow
else
    exit 0  # Good - fast enough
fi

Finding When a Test Started Failing

1
2
3
4
git bisect start
git bisect bad HEAD
git bisect good v1.0.0
git bisect run pytest tests/test_auth.py::test_login

Finding When a File Changed

1
2
3
#!/bin/bash
# Did this file exist with specific content?
grep -q "specific pattern" ./path/to/file.py

Finding a Build Breakage

1
2
3
4
git bisect start
git bisect bad HEAD
git bisect good last-known-good
git bisect run make

Tips and Tricks

Start with Terms You Understand

If “good” and “bad” feel backwards for your use case:

1
2
3
4
5
# Define custom terms
git bisect start --term-old=working --term-new=broken

git bisect broken  # Current state
git bisect working v1.0.0

Or for features:

1
git bisect start --term-old=without --term-new=with

Bisect from a Script’s Perspective

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#!/bin/bash
# Full automated bisect

git bisect start
git bisect bad HEAD
git bisect good $LAST_GOOD_SHA
git bisect run ./test.sh

# Get the bad commit
BAD_COMMIT=$(git bisect view --oneline | head -1 | cut -d' ' -f1)

git bisect reset

echo "Bug introduced in: $BAD_COMMIT"
git show $BAD_COMMIT

Handle Flaky Tests

1
2
3
4
5
6
7
8
9
#!/bin/bash
# Run test multiple times to handle flakiness

for i in {1..3}; do
    if ./flaky-test.sh; then
        exit 0  # Passed at least once = good
    fi
done
exit 1  # Failed all attempts = bad

When Bisect Doesn’t Help

Bisect assumes the bug was introduced in a single commit. It doesn’t work well for:

  • Merge conflicts: The bug might only appear after a merge
  • Multiple interacting changes: Two commits together cause the bug
  • Environment-dependent bugs: Works on some machines, not others
  • Data-dependent bugs: Only fails with specific data

For these, you might need to:

  1. Bisect to narrow the range
  2. Manually inspect commits in that range
  3. Use git log -p to review changes

Quick Reference

CommandPurpose
git bisect startBegin bisecting
git bisect bad [commit]Mark commit as having the bug
git bisect good [commit]Mark commit as not having the bug
git bisect skipSkip untestable commit
git bisect resetEnd bisect, return to original HEAD
git bisect run <script>Automate with test script
git bisect logShow bisect history
git bisect visualizeOpen gitk to visualize

Next time you’re staring at a bug that “wasn’t there before,” reach for git bisect. In a few minutes of binary search, you’ll have the exact commit—and usually, the fix becomes obvious once you know where to look.