It finds the culprit among thousands of commits in a dozen steps, often fully automatically.
When something worked in v2.3.0 and is broken now, the bug was introduced by one commit in between. `git bisect` binary-searches that range: it checks out the midpoint, you say whether it's good or bad, and it halves the range each time. 1,000 commits take about 10 steps.
The manual flow:
git bisect start
git bisect bad # current commit is broken
git bisect good v2.3.0 # this one was fineGit checks out a commit in the middle. Test it, then answer git bisect good or git bisect bad. Repeat until Git prints "<hash> is the first bad commit". git bisect reset returns you to where you started.
If a commit can't be tested (it doesn't build, for example), git bisect skip picks a nearby one instead.
The real power is `git bisect run <script>`: Git runs the script at each step and reads its exit code. 0 means good, 125 means skip, any other code from 1 to 127 means bad. A failing test case makes a perfect script, and the whole hunt runs unattended.
Bisect works best when every commit builds and passes tests on its own, one more reason for small, self-contained commits. For merge-heavy histories, git bisect start --first-parent follows only the mainline, so it identifies which merged PR caused the issue.
The terms don't have to be good and bad: git bisect start --term-old=slow --term-new=fast finds when something improved.
Example
cat > /tmp/check.sh <<'END'
#!/bin/sh
make -s build || exit 125 # can't build: skip this commit
./bin/app --selftest-date-parsing # exits non-zero when the bug is present
END
chmod +x /tmp/check.sh
git bisect start HEAD v2.3.0 # bad, then good
git bisect run /tmp/check.sh
# 7e1f3c2 is the first bad commit
# Author: ... Date: ... "Switch date parser to strptime"
git bisect log > bisect.txt # keep a record
git bisect resetRemember
- Bisect halves the suspect range each step: ~log2(n) tests
- git bisect run automates it: exit 0 good, 125 skip, 1-127 bad
- Always finish with git bisect reset
Watch out for
- Writing a run script that exits non-zero for unrelated reasons (like a build failure), so bisect blames the wrong commit — exit 125 to skip.