git bisect — Finding the Broken Commit
What It Does
git bisect performs a binary search through commit history to efficiently find the exact commit that introduced a bug. Instead of checking commits one by one (which could take hours on a large repo), bisect narrows the search logarithmically — finding the culprit in log₂(N) steps.
For 1000 commits, bisect finds the bad commit in at most 10 steps.
Basic Manual Bisect
Step 1 — Start bisect and mark the boundaries
git bisect start
# Mark the current state as "bad" (the bug is present here)
git bisect bad HEAD
# Mark a known good commit (the bug was not present here)
git bisect good v1.1.0
# or by SHA:
git bisect good a3f9bc2
Git checks out the commit at the midpoint of the range.
Step 2 — Test and mark each commit
After each checkout, test whether the bug is present:
# If the bug IS present at this commit:
git bisect bad
# If the bug is NOT present at this commit:
git bisect good
Git narrows the range with each answer and checks out the next midpoint.
Step 3 — Git identifies the culprit
After enough answers, Git outputs:
b7e2a1f is the first bad commit
commit b7e2a1f
Author: Bob Johnson <bob@example.com>
Date: Mon Jan 15 14:22:10 2024
refactor(transactions): extract date range logic to utility class
src/main/java/com/example/util/DateRangeUtils.java | 42 +++++
1 file changed, 42 insertions(+)
Step 4 — End bisect and return to your branch
git bisect reset
# HEAD is now back on your original branch
Automated Bisect with a Script
The real power of bisect is automation. Provide a script that returns exit code 0 for "good" and non-zero for "bad" — Git will run the entire search unattended.
Example: bisect using a Maven test
git bisect start
git bisect bad HEAD
git bisect good v1.1.0
# Run a specific test — pass/fail determines good/bad automatically
git bisect run ./mvnw -q test \
-Dtest="TransactionServiceTest#findTransactions_dateRangeFilter_returnsCorrectResults"
Git runs your test at each midpoint commit, automatically marking good/bad based on the exit code, and delivers the result without any manual input.
Example: bisect using a shell script
#!/bin/bash
# scripts/bisect-check.sh
# Build silently
./mvnw -q package -DskipTests 2>/dev/null || exit 125
# Exit 125 = "skip this commit" (e.g., build is broken — not the bug we're hunting)
# Run the specific test
./mvnw -q test -Dtest="TransactionServiceTest#findTransactions_emptyResult"
git bisect run bash scripts/bisect-check.sh
If a commit can't be tested (e.g., compile error unrelated to the bug), exit with 125. Git will skip that commit and move to the next one.
Bisect with Terms (Custom Good/Bad Labels)
For regressions that are not clearly "good/bad" (e.g., performance changes):
git bisect start --term-old fast --term-new slow
git bisect slow HEAD # current is slow
git bisect fast v1.1.0 # v1.1.0 was fast
# Git now prompts "is this commit fast or slow?"
git bisect fast # this commit is fast
git bisect slow # this commit is slow
Bisect Log and Replay
Save the bisect session to replay later:
git bisect log > bisect-session.log
# Replay a saved session
git bisect reset
git bisect replay bisect-session.log
Tips for Effective Bisect
| Tip | Detail |
|---|---|
| Write a reproducible test first | Automate with bisect run for speed and accuracy |
| Use tags as boundaries | git bisect good v1.0.0 — release tags are reliable known-good points |
| Skip broken builds | Exit 125 from your script to skip non-compilable commits |
| Check reflog after | git reflog shows all commits visited during bisect |
Once found, git show | git show <bad-commit> shows the exact diff that introduced the bug |
On a repo with 500 commits between the last known-good release and HEAD, manually checking each commit would take all day. git bisect run with an automated test finds the exact culprit in under 10 steps — usually in a few minutes. Write the test first.