
Turn a vague regression into a search problem
A report becomes incorrect after several weeks of changes. Randomly inspecting commits is slow. Given a verified good revision, a bad revision and a repeatable failure, git bisect narrows the history through binary search. Its usefulness depends on a clear definition of bad behavior.
Create the smallest input that reproduces the problem. Run the same check at both ends and derive the expected answer from the product rule. Avoid live services, current time and untracked files. Stabilize intermittent failures before automating the search; a mistaken classification can send later decisions down the wrong branch.
Build a check that can judge old revisions
Save current work and use a clean test checkout because bisect switches revisions. In the commands, good-release is a placeholder for an actual verified tag or commit. HEAD identifies the current broken revision. Do not assume an old release passes merely because nobody reported the problem then.
For automated runs, zero means good, an appropriate nonzero status means bad, and 125 means the revision cannot be tested. An old revision that fails to build because its environment is unavailable is not necessarily a product regression. Excessive skipping can leave several possible culprits. Preserve the bisect log, environment and fixture with the result.
The first bad commit marks a behavioral change, not necessarily the only underlying cause. A configuration or dependency update may expose an older flaw. Inspect the diff and convert the reproducer into a regression test. Decide on a focused fix after understanding the change.
Code example and verification
The notes state the prerequisites and expected outcome. Run the example in a test environment, then adapt it to your project’s data and constraints.
1. Terminal commands in a clean disposable checkout
# Replace good-release with a verified good tag or commit.
# tools/check_regression.py must exist at every tested revision,
# or be supplied from a fixed path outside the checkout.
git bisect start
git bisect bad HEAD
git bisect good good-release
git bisect run python tools/check_regression.py
git bisect log
git bisect resetRun one command at a time after choosing a verified good revision. The check should return 0 for good behavior, 1 for the target regression and 125 for an untestable revision. This workflow depends on your repository and is not a standalone executable fixture.
2. Build a disposable repository with a known regression
This creates a new temporary repository and configures author identity only there. The first three commits are good and the fourth introduces the defect. The check lives outside the checkout so historical revisions cannot remove it.
# make_bisect_demo.py; requires git on PATH.
from pathlib import Path
import subprocess
import tempfile
repo = Path(tempfile.mkdtemp(prefix="liyan-bisect-demo-"))
def git(*args):
return subprocess.check_output(["git", "-C", str(repo), *args], text=True).strip()
git("init", "--quiet")
git("config", "user.name", "Bisect Demo")
git("config", "user.email", "demo@example.test")
for index in range(6):
result = 100 if index < 3 else 99
(repo / "calc.py").write_text(
f"# revision {index + 1}\ndef total():\n return {result}\n", encoding="utf-8"
)
git("add", "calc.py")
git("commit", "--quiet", "-m", f"step-{index + 1}")
if index == 0:
git("tag", "demo-good")
if index == 3:
git("tag", "demo-first-bad")
check = repo.parent / (repo.name + "-check.py")
check.write_text(
"import pathlib, runpy, sys\n"
"if not pathlib.Path('calc.py').exists(): sys.exit(125)\n"
"module = runpy.run_path('calc.py')\n"
"sys.exit(0 if module['total']() == 100 else 1)\n",
encoding="utf-8",
)
print("REPOSITORY:", repo)
print("CHECK SCRIPT:", check)
print("EXPECTED BAD:", git("rev-parse", "demo-first-bad"))Keep the printed paths and expected commit ID. The checker returns zero for correct output, one for the regression and 125 when the file is unavailable. The fixture needs neither a remote nor network access and leaves the project repository untouched.
3. Run the search and preserve the evidence
Replace the placeholders with the fixture’s printed paths and run the commands individually. -C targets that repository without changing the terminal’s working directory.
# Substitute both paths printed by make_bisect_demo.py.
git -C "/path/to/liyan-bisect-demo-..." bisect start HEAD demo-good
git -C "/path/to/liyan-bisect-demo-..." bisect run python "/path/to/liyan-bisect-demo-...-check.py"
git -C "/path/to/liyan-bisect-demo-..." rev-parse refs/bisect/bad
git -C "/path/to/liyan-bisect-demo-..." bisect log
git -C "/path/to/liyan-bisect-demo-..." bisect resetrefs/bisect/bad must match EXPECTED BAD. The log records classifications and reset restores the initial HEAD. If skipped revisions leave several candidates in a real investigation, do not report a single certain culprit.
4. Automatically verify the discovered commit
Save outside the demo repository and pass the two printed paths. The repository-name guard helps avoid targeting an unrelated checkout. finally leaves bisect even when an assertion fails.
# verify_bisect_demo.py REPOSITORY CHECK_SCRIPT
from pathlib import Path
import subprocess
import sys
repo = Path(sys.argv[1]).resolve()
check = Path(sys.argv[2]).resolve()
if not repo.name.startswith("liyan-bisect-demo-"):
raise SystemExit("Use only the disposable repository from this article")
def git(*args):
return subprocess.check_output(["git", "-C", str(repo), *args], text=True).strip()
expected = git("rev-parse", "demo-first-bad")
try:
git("bisect", "start", "HEAD", "demo-good")
git("bisect", "run", sys.executable, str(check))
assert git("rev-parse", "refs/bisect/bad") == expected
print("Found the expected regression:", expected)
finally:
git("bisect", "reset")Expect Found the expected regression with the fourth commit. The generated repository and checker are disposable teaching artifacts. In a real project, replace total() with a small stable reproducer and retain it as a regression test.
Interpret the first bad commit carefully
For a student exercise, introduce a calculation error into one commit of a small repository, then locate it with a deterministic check. Finish with bisect reset. The useful skill is connecting history to test evidence so another developer can reproduce the finding.
Implementation checklist
- Verify both endpoint classifications.
- Use a clean test checkout.
- Distinguish a bad result from an untestable revision.
- Save the log and leave bisect with reset.
References
Editorial guidance: Liyan Knowledge.



