Lesson 6 / 25
Finding the Breaking Commit With git bisect
Binary search through history.
Automated binary search
When something used to work, git bisect finds the commit that broke it by binary search: mark a known good and a known bad commit, and git checks out the midpoint for testing. With git bisect run <script> the test is automatic: exit code 0 means good, non-zero means bad. Finding the breaking commit narrows the search to its diff, which is often the best context for a fix (or a candidate for a straight revert).
Bisect over eight commits, run
I ran this with Python 3 (standard library) and, where it uses git, real git in a throwaway temporary repository. Candidate patches are written by hand to stand in for model output. In a temporary repository with eight commits, the sixth introduces an off-by-one for large inputs. git bisect run with a one-line check script identifies "change 6" as the first bad commit after testing three commits.
import os, subprocess, tempfile
d = tempfile.mkdtemp()
def git(*a): return subprocess.run(["git", *a], cwd=d, capture_output=True, text=True).stdout
git("init", "-q"); git("config", "user.email", "demo@example.com"); git("config", "user.name", "demo")
for i in range(1, 9):
body = "def add(a, b):\n return a + b\n" if i < 6 else "def add(a, b):\n return a + b + (1 if a > 100 else 0)\n"
open(os.path.join(d, "calc.py"), "w").write(body + f"# revision {i}\n")
git("add", "."); git("commit", "-qm", f"change {i}")
open(os.path.join(d, "check.sh"), "w").write('python3 -c "from calc import add; assert add(200, 1) == 201"\n')
git("bisect", "start", "HEAD", "HEAD~7")
out = git("bisect", "run", "sh", "check.sh")
first_bad = [l for l in out.splitlines() if "is the first bad commit" in l][0].split()[0]
print("first bad commit message:", git("log", "-1", "--format=%s", first_bad).strip())
print("commits tested by bisect:", out.count("running"))
git("bisect", "reset")
Output:
first bad commit message: change 6 commits tested by bisect: 3
Make the check script fast and exact
A bisect script should test only the failing behaviour and exit with a clear code; flaky checks send bisect to the wrong commit.
Quick check: What does git bisect run need from the test script?
- A new branch per commit
- A screenshot
- A pull request
- Exit code 0 for good commits and non-zero for bad ones
Answer
Exit code 0 for good commits and non-zero for bad ones — Exit codes drive the binary search.