git bisect
"This worked last month and doesn't now, but I don't know which of the 200 commits since broke it" is exactly the problem git bisect solves. Rather than checking commits one at a time from the start, it performs a binary search through history — finding the exact breaking commit in roughly log₂(N) steps instead of N.
The Core Idea
You tell git one commit you know was good (the bug wasn't present) and one you know is bad (the bug is present — often just the current commit). Git checks out a commit roughly halfway between them, and asks you: good or bad? Based on your answer, it narrows the search to one half and repeats — exactly the same algorithm as binary-searching a sorted list.
Running a Bisect
$ git bisect bad # HEAD is confirmed broken
$ git bisect good v1.4.0 # this tagged release definitely worked
Bisecting: 47 revisions left to test after this (roughly 6 steps)
[a1b2c3d] Refactor session handling
# Test the app at this checked-out commit...
$ git bisect good # bug NOT present here
Bisecting: 23 revisions left to test after this (roughly 5 steps)
# ...repeat, testing and reporting each time...
f9e8d7c is the first bad commit
commit f9e8d7c
Author: ...
Update token expiry check
$ git bisect reset
git bisect reset returns you to the branch/commit you were on before starting. Forgetting this step is the most common bisect mistake, leaving you confusingly "detached" afterward.
Automating the Test Step
If the "is the bug present" check can be scripted — a specific test passing or failing, a command exiting with a particular code — git bisect run automates the entire process, no manual good/bad reporting at all.
$ git bisect bad HEAD
$ git bisect good v1.4.0
$ git bisect run npm test -- --grep "session expiry"
running npm test -- --grep "session expiry"
...
f9e8d7c is the first bad commit
Git runs the given command at each candidate commit, treating exit code 0 as "good" and any non-zero exit code as "bad" — turning a search that might've taken an afternoon of manual testing into something that completes unattended in the time it takes to run the test suite a handful of times.
Command Reference
| Command | What it does |
|---|---|
| git bisect start | Begins a bisect session |
| git bisect bad [<commit>] | Marks a commit (default: current) as exhibiting the bug |
| git bisect good [<commit>] | Marks a commit as not exhibiting the bug |
| git bisect run <command> | Automates testing using a script's exit code instead of manual reporting |
| git bisect reset | Ends the session, returning to your original branch/commit — don't forget this |
Chapter 8 Quick Reference
- git bisect finds a regression's exact introducing commit via binary search through history
- ~log₂(N) steps, not N — roughly 8 steps for 200 commits, not 200 individual checks
- Flow: bisect start → mark one bad, one good → test each checked-out commit, report good/bad → repeat until git identifies the culprit
- git bisect run <command> — fully automates the process if the check can be scripted
- Always finish with git bisect reset — returns you from the detached intermediate state to your actual branch
- Pick a genuinely reliable "good" commit — a tagged release is usually safer than an arbitrary recent one
- Next chapter (Scenario): force-push recovery after a history rewrite goes wrong across a team