The test fails on main. It passed a week ago. Twelve commits landed in between, most of them documentation, and nobody remembers which one touched the code. You could read all twelve diffs. Or you could let Git binary-search them in four steps.
git bisect is the tool. It is older than most of the people who avoid it, and it needs three things: a commit you know is bad, a commit you know is good, and a way to tell the two apart. Everything below was run in a disposable repository (git 2.43.0); the output is real.
The setup: a calculator that stopped handling negatives
A shell script that sums its arguments, a test that checks 2 3 → 5 and 10 -4 → 6, and twelve commits — ten of which only append to NOTES.md. One "performance" commit changed a line in the script.
./test.sh && echo PASS || echo FAIL
FAIL
git log --oneline | head -3
git rev-list --count HEAD
13e3f4d Docs: note 10
de66fcd Docs: note 9
ee74893 Docs: note 8
12
Manual bisect: the first step, by hand
Tell Git the two ends. HEAD is bad; the first commit is known good (the test was written with it):
git bisect start
git bisect bad
git bisect good c7c12bd
status: waiting for both good and bad commits
status: waiting for good commit(s), bad commit known
Bisecting: 5 revisions left to test after this (roughly 3 steps)
[ab66c706055338cda0ca242c3deda07a5b8f5257] Docs: note 5
Git checked out the middle commit. Run the test there and report:
./test.sh && echo PASS || echo FAIL
PASS
So git bisect good, and Git halves the remaining range again. "Roughly 3 steps" is log₂ of twelve — the same three or four steps whether the culprit is the second commit or the eleventh. At each stop you run the test and say good or bad; git bisect reset returns you to where you started.
Automated bisect: hand Git the test
If the test is a command that exits 0 on pass and non-zero on fail — which test.sh is — you do not need to be in the loop at all:
git bisect start HEAD c7c12bd
git bisect run ./test.sh
running './test.sh'
Bisecting: 2 revisions left to test after this (roughly 2 steps)
running './test.sh'
Bisecting: 0 revisions left to test after this (roughly 1 step)
running './test.sh'
Bisecting: 0 revisions left to test after this (roughly 0 steps)
running './test.sh'
ee25549ef0e8991131dcb8fe06a5dfa8398eb3d9 is the first bad commit
commit ee25549ef0e8991131dcb8fe06a5dfa8398eb3d9
Author: Lab User <lab@example.com>
Perf: simplify addition loop
calc.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
bisect found first bad commit
Four test runs. Then:
git bisect reset
git show ee25549 -- calc.sh
-total=0; for n in "$@"; do total=$((total + n)); done; echo "$total"
+total=0; for n in "$@"; do total=$((total + ${n#-})); done; echo "$total"
${n#-} strips a leading minus sign. Someone "simplified" the loop and turned every negative into a positive. The commit message says "Perf"; the diff says otherwise. That is the whole value of bisect: it takes you to the diff instead of the story.
The three things that make it work
A test that can run at any commit. bisect run checks out old commits, so the test must not depend on files that arrived later. Keep the test script outside the range if it was added recently — put it in a temp directory and call it by absolute path — or accept that the range starts where the test does.
Exit codes, not output. run reads the exit status: 0 means good, 1–127 means bad, except that 125 means "cannot test this commit, skip it" — use it for commits that do not build. Any other code aborts the bisect (the manual's words).
Honest endpoints. If the "good" commit is not actually good, bisect converges on the wrong answer with the same confidence. Run the test at both ends first; it costs two runs and saves an afternoon.
Beyond the toy
-
Test a specific thing, not the whole suite:
git bisect run npm test -- --grep "negative". Faster, and it fails for the right reason. -
Skip commits by hand:
git bisect skipwhen a commit cannot be tested; Git works around it. -
Reuse a session:
git bisect log > bisect.txt, latergit bisect replay bisect.txt. - Merges: bisect walks the DAG correctly; the first bad commit can be a merge, and that is a real answer.
The lab has the same repository, a second regression to find without run, and the reflog step that recovers you if you bisect on a dirty tree: Find a regression with git bisect. It is one of twenty labs, each in a disposable repository with real output.
Top comments (0)