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.

  • Git
  • Debugging
  • Command Line