Something worked last month and is broken now, and there are 200 commits in between. Instead of reading them all, git bisect performs a binary search through history: you tell Git one good and one bad commit, it checks out the middle, you test, and it halves the range each time. 200 commits take about 8 tests.
Manual bisect
git bisect start
git bisect bad # the current commit is broken
git bisect good v1.7.0 # this tag (or hash) worked
# Git checks out a commit in the middle:
# Bisecting: 99 revisions left to test after this (roughly 7 steps)
Test the checked-out version (build it, run it, reproduce the bug), then tell Git:
git bisect good # this commit works
# or
git bisect bad # this commit has the bug
Repeat until Git reports:
3f2a1c9 is the first bad commit
commit 3f2a1c9…
Author: …
Switch session storage to Redis
Finish and return to where you started:
git bisect reset
Automate it with git bisect run
If you can test with a script that exits 0 for good and 1–127 (except 125) for bad, Git does everything for you:
git bisect start HEAD v1.7.0
git bisect run ./test-bug.sh
git bisect reset
Example test script:
#!/bin/sh
# test-bug.sh
npm ci --silent || exit 125 # 125 = "can't test this commit, skip it"
npm test -- --grep "invoice totals" > /dev/null 2>&1
Or a one-liner for a specific symptom:
git bisect run sh -c 'make >/dev/null 2>&1 || exit 125; ./app --selftest | grep -q "OK"'
Skip untestable commits
Some commits don’t build, or are unrelated:
git bisect skip # skip the current commit
git bisect skip v1.7.1..v1.7.2 # skip a range
Exit code 125 in bisect run does the same automatically.
Useful extras
git bisect log > bisect.log # save the session
git bisect replay bisect.log # replay it (e.g. after correcting a mistake)
git bisect visualize # show remaining candidates (gitk or log)
git bisect terms --term-old=fast --term-new=slow # custom terms, e.g. for performance regressions
With custom terms you then run git bisect fast / git bisect slow, which is clearer than “good/bad” when hunting a performance change.
Tips for reliable results
- Reproduce the bug reliably first. Flaky tests give wrong answers. Add retries in the test script, if needed.
- Bisecting assumes the bug appeared once and stayed. Intermittent or reintroduced bugs confuse it.
- For apps with dependencies, reinstall them in each step (
npm ci,composer install,pip install -r), so you test the right versions. - Database migrations can make old commits incompatible with your current local database. Test against a fresh or disposable database (Docker makes this easy). See Docker Compose tutorial.
- Once you know the culprit commit, read its diff with
git show <hash>, and write a regression test before fixing.
Bisect for configuration and infrastructure
The same idea works beyond code: Ansible playbooks, Nginx config repositories, or /etc history with etckeeper (git -C /etc bisect …). See etckeeper.
Quick reference
| Task | Command |
|---|---|
| Start | git bisect start |
| Mark | git bisect good <rev> / git bisect bad <rev> |
| Skip | git bisect skip |
| Automate | git bisect run <script> (0 good, 1 bad, 125 skip) |
| Finish | git bisect reset |
| Save / replay | git bisect log / git bisect replay |
Related: Git commands cheat sheet and Linux troubleshooting methodology.