Git & GitHub · lesson 10 of 12
Finding the Bad Commit with git bisect
Use git bisect run with a real automated test script to binary-search five commits and pinpoint the exact one that silently broke a function.
By SkillByExample · Published
Five commits, one of them silently changed add(2, 2) from 4 to 5. Manually
checking each one is fine at five — at fifty it isn’t. git bisect run automates the
whole search with a real test script.
1) A commit history with a hidden regression
$ git log --oneline -5
0b276a2 Add mathlib.divide
f70cf08 Refactor add for readability
3974231 Add mathlib.subtract
f257dd3 Add mathlib.multiply
c6e952c Add mathlib.add
One of these five silently broke add. git log -p would find it by reading every
diff — bisect finds it by testing, not reading.
2) Confirm the bug exists at HEAD, and didn’t exist at the start
$ node check_add.mjs; echo "exit: $?"
exit: 1
check_add.mjs calls add(2, 2) and exits 0 if it equals 4, 1 otherwise. At the
current HEAD (0b276a2) it fails. The first commit (c6e952c) is the known-good
baseline — bisect needs one confirmed-bad and one confirmed-good endpoint.
3) Let bisect run the test at every step
$ git bisect start HEAD c6e952c
$ git bisect run node check_add.mjs
Bisecting: 1 revision left to test after this (roughly 1 step)
[3974231...] Add mathlib.subtract
running 'node' 'check_add.mjs'
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[f70cf08...] Refactor add for readability
running 'node' 'check_add.mjs'
f70cf0825d00f3ab95bb5b4c8a18f7a3b03c0595 is the first bad commit
commit f70cf0825d00f3ab95bb5b4c8a18f7a3b03c0595
Author: Dev <dev@example.com>
Refactor add for readability
mathlib.mjs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
bisect found first bad commit
git bisect start HEAD c6e952c marks the bad and good endpoints in one line
(bad first, good second). Bisect checked out 3974231 (“subtract”), ran the script,
saw exit 0 (good), narrowed the range, checked out f70cf08 (“refactor”), saw exit
1 (bad) — and with no commits left between, declared it the culprit. Two checkouts
found the bug in a five-commit history; the same halving means a 1,000-commit history
takes about 10.
4) Return to normal
$ git bisect reset
Previous HEAD position was f70cf08 Refactor add for readability
Switched to branch 'main'
Bisect leaves you in a detached HEAD at the guilty commit so you can inspect it;
reset returns you to the branch you started from.
Practice
-
Run
git bisect start, thengit bisect badandgit bisect good <hash>as three separate commands (instead of one combinedstart HEAD c6e952c) to see the step-by-step form the automated version above skips past. -
Write a test script for a different bug — pick any earlier lesson’s scratch repo, introduce a silent regression a few commits back, and confirm
git bisect runfinds it without you reading a single diff by eye. -
Run
git bisect logafter a completed bisect to see the exact sequence of good/bad judgments it made, andgit bisect replaywith that log to rerun the same search.Solution
$ git bisect log > bisect.log $ git bisect reset $ git bisect replay bisect.logReplaying is useful for sharing a bisect session with a teammate without them re-running the test script themselves.
-
Mark a commit
git bisect skipinstead of good/bad (simulating one that doesn’t build, so it can’t be tested) and observe bisect route around it. -
Compare the number of steps
git bisecttook against how many commits exist between good and bad — confirm it’s proportional to log2(n), not n.