git bisect: the bug-finder you forgot
bug shows up. you don’t know when it was introduced. you scroll through commits looking for the obvious culprit. you don’t find it.
git bisect is binary search over your history. you tell it a known-bad commit (HEAD, usually) and a known-good commit (last week’s release, maybe). it checks out the midpoint. you test. you say “good” or “bad.” it bisects again. log₂(n) tests later, it tells you the exact commit that broke things.
the basic flow
git bisect start
git bisect bad # current commit is broken
git bisect good v2.4.1 # this tag was fine
# git checks out the midpoint
# run your test
git bisect bad # or `good`
# repeat until done
git bisect reset
automating with bisect run
the move that turns this into magic: git bisect run <command>. you give it a script that returns 0 when the code is good and non-zero when it’s broken. git bisects automatically.
git bisect start HEAD v2.4.1
git bisect run npm test -- --grep "the-broken-test"
walk away. come back. git tells you the commit that introduced the failing test.
i used this last week to find a regression in a typescript build that took 200ms longer than it should. the script: npm run build && [ $(stat -c %s dist/main.js) -lt 800000 ]. four bisects later, git pointed at a commit that swapped a tree-shakable import for a default one. five-line fix.
caveats:
- bisect needs your test to be deterministic. if it flakes, it’ll lie.
- bisect needs the broken commit to actually be in your history (no squash-and-merge silliness covering it up).
- bisect can’t fix a bug that’s been there forever. it finds introductions, not causes.
the rest of git is a vcs. bisect is a debugger.