Course outline

Git and GitHub / Advanced: trust, scale, and recovery / Lesson 13

History investigation and automated bisect

Turn a reproducible regression into a bounded search through history while separating a suspect commit from a complete root cause.

About 4 min readContent reviewed
On this page

Lesson goals

  • Choose known-good and known-bad boundaries.
  • Build a deterministic predicate with correct bisect exit semantics.

Define one reproducible regression

Bisect narrows history by repeatedly classifying selected revisions as good or bad for one predicate. It cannot compensate for an unstable test, changing dependencies, or a vague symptom. Identify a known-good ancestor and a known-bad revision under comparable conditions. Preserve the current work and run the search in a separate temporary checkout. Record tool versions, data, and environment assumptions. A commit that first exposes failure may reveal a latent bug elsewhere, so the result is a strong investigative starting point rather than automatic proof that one author or line is solely responsible.

Make the classifier explicit

git bisect run interprets exit 0 as good and ordinary nonzero codes in its documented range as bad, with 125 reserved for skip. Certain command-not-found and execution errors should not be mistaken for a product regression; understand the documented status rules and fail the harness appropriately. The local script below checks a synthetic state file. Keep the script outside the historical checkout so switching revisions does not replace the diagnostic itself. A real test should distinguish unsupported historical builds from a genuinely bad behavior instead of marking every setup failure as bad.

python A synthetic bisect predicate stored outside the checkout
from pathlib import Path
import sys

path = Path("state.txt")
if not path.exists():
    sys.exit(125)
sys.exit(0 if path.read_text(encoding="utf-8").strip() == "good" else 1)

Save as check-notebook.py in the disposable lab parent. good content exits 0, other present content exits 1, and a missing file exits 125. It performs only a local read and does not contact services or change history.

The fragment assumes a temporary history with tags lab-good and lab-bad and a predicate in the parent directory. start establishes the bad and good boundaries in that order. run checks selected commits until it identifies the transition or explains ambiguity from skipped revisions. Capture the bisect log before resetting the operation. The exact selected commits and number of steps depend on the graph. No hash is invented in this lesson. A large skip region can leave several possible first-bad commits, which should remain an explicit uncertainty rather than being reported as one definitive result.

bash Search a synthetic local history and restore the lab checkout
git bisect start lab-bad lab-good
git bisect run python ../check-notebook.py
git bisect log
git bisect reset

Requires the stated local tags, clean lab, and deterministic predicate. reset here ends bisect and returns from its temporary checkout state; it is not git reset --hard. No actual workspace branch, remote, or hosted service is involved.

Recheck the transition and surrounding assumptions

After the search, test the suspected revision and an appropriate predecessor again under the same environment. Inspect the patch, dependencies, configuration changes, and merge context. A regression may require an interaction between several changes or a dependency version absent from the repository. Build a focused regression test and verify the proposed fix on the current supported branch. Avoid editing historical commits merely to make them build unless the harness records the adaptation clearly. Keep the original classification evidence and note any skipped revisions or environment limits in the investigation record.

Combine bisect with careful historical reading

Use log content searches, blame, show, and range comparisons to understand why the transition matters. These tools answer different questions and can be affected by history simplification, rename detection, and merge traversal. First-parent history can help identify integration points while detailed ancestry can reveal the contributing change. Keep attribution constructive: history records what was committed, not every discussion or operational condition. A useful conclusion states the reproducible symptom, tested boundaries, observed transition, root-cause reasoning, fix validation, and remaining limits. This is more actionable than reporting a hash with no explanation.

Common errors and fixes

Bisect returns inconsistent suspects

Why it happens
The predicate or environment is nondeterministic.
How to fix it
Stabilize inputs and repeat controlled boundary checks.
Prevent it next time
Validate the predicate before searching.

Old build failures are labeled bad behavior

Why it happens
Setup errors share the regression exit path.
How to fix it
Distinguish unsupported setup and use skip only where justified.
Prevent it next time
Document historical runtime and dependency requirements.

One identified commit is treated as the entire cause

Why it happens
Transition detection is confused with causal explanation.
How to fix it
Recheck the transition and inspect interactions.
Prevent it next time
Require a regression test and verified fix.

Check your understanding

1. What should a predicate do when the target file does not exist in an old revision?

Show worked solution

Follow the experimentโ€™s definition. If the behavior cannot be evaluated there, a justified skip can be appropriate; if absence is itself the regression, classify it accordingly. Do not automatically equate missing setup with a product failure.

2. Bisect finds a merge commit. How can investigation continue?

Show worked solution

Inspect both parent histories and the merge result, including conflict resolutions and integration assumptions. Reproduce the combined behavior and determine whether the failure came from one side or their interaction. Preserve the original bisect evidence.

Key takeaways

  • A deterministic predicate is essential to trustworthy bisect.
  • Skipped history and environment limits remain uncertainty.
  • A first-bad commit begins root-cause analysis.

Official references