What you will achieve
You will use git bisect with an automated test script to identify the exact commit that introduced a regression. The technique scales from "the bug appeared sometime this month" to thousand-commit windows in minutes.
Set up a sandbox with a planted bug
mkdir bisect-tutorial && cd bisect-tutorial
git init
cat > calc.js <<'EOF'
function add(a, b) { return a + b; }
module.exports = { add };
EOF
git add calc.js && git commit -m "Initial calc"
for i in 1 2 3 4 5 6 7 8 9 10; do
echo "// touched: $i" >> calc.js
git commit -am "Tweak $i"
done
# Plant a bug at commit 5 (counting from 1)
git checkout HEAD~5
sed -i.bak 's/a + b/a - b/' calc.js && rm calc.js.bak
git commit -am "Refactor add (oops)"
git checkout main
git rebase HEAD~5
(In real projects you do not plant bugs - this is for the demo.)
Write a reproducer
cat > test.js <<'EOF'
const { add } = require('./calc');
if (add(2, 3) !== 5) {
console.error('FAIL: add(2,3) should be 5');
process.exit(1);
}
EOF
Run it - it fails. We know HEAD is bad. Find a known-good baseline:
git checkout HEAD~12
node test.js # passes
git checkout main
Manual bisect
git bisect start
git bisect bad HEAD
git bisect good HEAD~12
# Git checks out the midpoint
For each checkout, run the test:
node test.js
# if pass:
git bisect good
# if fail:
git bisect bad
After a few steps, Git prints:
abc1234 is the first bad commit
Author: ...
Date: ...
Refactor add (oops)
Reset
git bisect reset
Automated bisect
Even better, let bisect run unattended:
git bisect start HEAD HEAD~12
git bisect run node test.js
Bisect runs your script at each candidate. Exit 0 = good; non-zero = bad; 125 = skip. Within seconds, Git prints the offending SHA.
Skipping untestable commits
If a checkout cannot be evaluated (broken build, unrelated dependency missing), skip:
git bisect skip
# or in a script, exit 125
Customising terms
For a regression where the issue is not "broken" but "slow":
git bisect start --term-old=fast --term-new=slow
git bisect slow HEAD
git bisect fast v2.4.0
What makes bisect work well
- Atomic commits. Each commit builds and tests pass; bisect can evaluate any commit.
- Reproducible tests. Flaky tests poison bisect.
- Deterministic bug. Intermittent bugs require scripted retries.
Real-world example
# Production bug appeared between v3.0.0 and HEAD
git bisect start HEAD v3.0.0
git bisect run ./scripts/reproduce-issue.sh
Twenty minutes later, you have a 50-line commit to investigate instead of 50,000.
The mental model
Bisect performs binary search through commits. log2(N) checks identify the first bad commit. For 1024 commits, ten checks. For 16k commits, fourteen.
Tips
- Wrap the reproducer in a script - even a one-liner. Reproducibility matters.
- Test the reproducer on known-good and known-bad commits before
bisect run. - For first-parent only history, use
--first-parentto skip merged feature branch interiors.
Bisect turns "I have no idea where this regression came from" into a 20-minute mechanical exercise. Use it the next time a bug bites; it will earn its place in your toolkit immediately.