git bisect Find the Breaking Commit Without Reading 500 Log Lines
Stop scanning git log for the commit that broke production. git bisect binary-searches your history in about 9 steps regardless of how many commits are in the range. Here’s the full workflow, an annotated real session, and the gotchas to watch for.
You pushed 50 commits over two weeks. Somewhere in there, something broke. The test that was passing three weeks ago now fails. git log --oneline shows 200 lines you don’t want to read.
You have options: stare at diffs until your eyes bleed, git checkout random commits and test manually, or use the tool that’s been in git since 2007 and ships with every installation.
git bisect does a binary search through your commit history. You mark one commit as broken, one as working, and it checks out commits in between — cutting the search space in half each time. Finding the culprit in 500 commits takes about 9 steps. Most developers never use it.
怎么运行的
The entire workflow is three commands to start:
git bisect start
git bisect bad # current state is broken
git bisect good v2.4.0 # last known-good tag or commit hash
Git checks out a commit midway between those two points. You test whether the bug exists, then report back:
git bisect good # bug is NOT present here
# or
git bisect bad # bug IS present here
Git cuts the range and picks the next midpoint. Repeat until it tells you abc1234 is the first bad commit. When you’re done:
git bisect reset # returns you to your original branch
No special flags, no config, no plugins. Works on any repo, on any git version you’re likely to have installed.
A Real Bisect Session (Annotated)
Here’s a complete session. A function that was returning correct JSON started returning null after some recent refactors — no obvious culprit in recent commits:
$ git bisect start
$ git bisect bad HEAD # current commit is broken
$ git bisect good 3f8a12b # commit from 2 weeks ago, worked fine
Bisecting: 24 revisions left to test after this (roughly 5 steps)
[c9e41f0] Refactor: extract auth middleware
$ npm test -- --grep "parseResponse" # ← run your check here
# FAIL
$ git bisect bad
Bisecting: 12 revisions left to test after this (roughly 4 steps)
[a77b2d1] Fix: update retry logic in fetch wrapper
$ npm test -- --grep "parseResponse"
# PASS
$ git bisect good
Bisecting: 6 revisions left to test after this (roughly 3 steps)
[91e0bc3] Refactor: consolidate response handlers
$ npm test -- --grep "parseResponse"
# FAIL
$ git bisect bad
Bisecting: 2 revisions left to test after this (roughly 2 steps)
[b44c19f] Chore: remove deprecated serialization helpers
$ npm test -- --grep "parseResponse"
# FAIL
$ git bisect bad
Bisecting: 1 revision left to test after this (roughly 1 step)
[8d3e77a] Fix: normalize response envelope for new API version
$ npm test -- --grep "parseResponse"
# PASS
$ git bisect good
91e0bc3e1f4d is the first bad commit
commit 91e0bc3e1f4d
Author: Dev <dev@example.com>
Date: Mon Apr 14 11:23:07 2026
Refactor: consolidate response handlers
src/http/response.js | 14 +++---
$ git bisect reset # ← always do this when done
5 steps across a 50-commit range. That “consolidate response handlers” commit had quietly removed a serialization call that something downstream was depending on. Without bisect, you’d have been reading diffs for an hour.
Automating It
If you can express the check as a script — exits 0 for good, non-zero for bad — git bisect can run entirely hands-off:
git bisect start
git bisect bad HEAD
git bisect good v2.4.0
git bisect run npm test -- --grep "parseResponse"
Git checks out each midpoint, runs the script, and reports the first bad commit. For tests that run in under 10 seconds this is fast enough to walk away and come back with the answer.
Exit code semantics for the run script:
- 0 — commit is good
- 1–124 — commit is bad
- 125 — skip this commit (broken build, can’t test it)
使用 125 when a commit doesn’t compile or the test infra is broken at that point — bisect skips it and moves to the next candidate.
When Bisect Beats git blame
git blame tells you who last touched a line. Useful when you already know which line is wrong. git bisect is for when you don’t — when the symptom is behavioral and the cause could be anywhere.
Use bisect when:
- You can reproduce the regression but can’t trace it to a specific file
- Production behavior changed but no obvious suspect in recent history
- “This worked in v2.3, broken in v2.5” with 80+ commits between them
- A test started failing consistently around a certain date
When comparing output between two commits during bisect, pasting both into a text diff tool is often faster than reading unified diffs in the terminal — especially when the output is a blob of JSON or a serialized object.
常见陷阱
Don’t forget git bisect reset. Bisect checks out commits in detached HEAD state. If you switch branches mid-session without resetting, the bisect state will be stale and you’ll be confused. Always reset when done, even after finding the answer.
Merge commits can distort the range. If your range includes a long-lived feature branch that was merged in, bisect might land inside that branch’s commits in a non-obvious order. Run git log --oneline --no-merges good..bad first to understand what’s in the range.
Test reproducibility is everything. If your check is flaky — timing-sensitive, environment-dependent, or hitting an external service — bisect will give you garbage results. Make sure the test is deterministic before automating it.
The commit bisect identifies is where behavior changed, not necessarily where the bug lives. Sometimes a correct refactor exposed a pre-existing assumption elsewhere. Treat the result as “start reading here,” not “revert this and you’re done.”
If you debug git history regularly, the Git 备忘单 on IO Tools has bisect, blame, log flags, and reflog all on one page — worth bookmarking for the next time something mysterious breaks.
