git bisect

Course 3 · Ch 8
git bisect
Finding exactly which commit introduced a regression, in logarithmic time instead of checking every commit one by one

"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.

good bad git checks this one out — you test it
Each answer (good/bad) halves the remaining search space — ~200 commits resolves in about 8 steps, not 200

Running a Bisect

1
Start the bisect
git enters bisect mode, tracking the search internally.
2
Mark a known-bad commit (often just HEAD)
The point where you've confirmed the bug exists.
3
Mark a known-good commit
Any earlier point you're confident the bug wasn't present — a tagged release (Course 2, Ch5) is often a convenient, reliable choice.
4
Test the commit git checks out, report good or bad
Run your app/tests at this exact commit, then tell git the result. Repeat — git narrows the range each time.
5
Git announces the first bad commit
Once the range narrows to a single commit, bisect identifies it precisely — the exact commit that introduced the regression.
$ git bisect start
$ 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
$ # Always finish by returning to where you started
$ git bisect reset
Don't forget git bisect reset
Bisect leaves your working directory checked out at whatever intermediate commit it was testing — not your actual branch. 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 start
$ 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.

A good "good" commit matters more than people expect
Choosing a known-good commit that's genuinely far enough back (well before the regression could plausibly have been introduced) avoids accidentally narrowing the search to the wrong half if your assumption about "good" turns out to be wrong. A tagged release you're confident worked correctly, rather than an arbitrary recent commit, is usually the safer starting point.

Command Reference

CommandWhat it does
git bisect startBegins 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 resetEnds 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