Chapter 16Lesson 02~150 minutes

Bisect, Blame, Pickaxe Search, Merge Bases, and Repository Forensics: Guided Hands-On Workflow and Core Operations

Build a controlled regression history and operate manual/automated bisect, blame ignore-revision analysis, pickaxe -S/-G searches, exact diffs, and merge-base comparison against a diverged support line.

Regression labbisect runIgnore revisionsPickaxe

Learning objectives

  • Verify known-good/known-bad endpoints before manual and automated bisection.
  • Run git bisect start/good/bad/run/reset with a deterministic cross-shell oracle.
  • Use blame line ranges and ignore-revision analysis for a mechanical formatting commit.
  • Select complementary history candidates with -S and -G and inspect their patches.
  • Compute a support-line merge base and connect ancestry to branch-specific diffs.

1. Create a disposable regression repository

This repository contains ten linear trunk commits, one reproducible timeout regression, a later formatting-only change, and a support branch that diverges before the regression. Two small test scripts are committed from the baseline so they exist at every revision the bisect may check out.

Git Bash, Bash, or zsh

mkdir git-forensics-lab
cd git-forensics-lab
git init -b trunk repo
cd repo
git config user.name "Forensics Lab"
git config user.email "forensics-lab@example.invalid"

cat > service.conf <<'EOF'
timeout_ms=100
mode=safe
feature=off
EOF

cat > policy.txt <<'EOF'
owner=platform
threshold=10
mode=safe
EOF

cat > check-regression.sh <<'EOF'
#!/bin/sh
value=$(sed -n 's/^timeout_ms[[:space:]]*=[[:space:]]*//p' service.conf)
test -n "$value" || exit 125
test "$value" -le 300
EOF

cat > check-regression.ps1 <<'EOF'
$line = Get-Content service.conf | Where-Object { $_ -match '^timeout_ms\s*=' } | Select-Object -First 1
if (-not $line) { exit 125 }
$value = [int](($line -split '=',2)[1].Trim())
if ($value -le 300) { exit 0 } else { exit 1 }
EOF

git add .
git commit -m "C1: establish known-good service and test oracle"
C1=$(git rev-parse HEAD)

PowerShell setup alternative

New-Item -ItemType Directory git-forensics-lab | Out-Null
Set-Location git-forensics-lab
git init -b trunk repo
Set-Location repo
git config user.name "Forensics Lab"
git config user.email "forensics-lab@example.invalid"

@('timeout_ms=100','mode=safe','feature=off') | Set-Content service.conf
@('owner=platform','threshold=10','mode=safe') | Set-Content policy.txt

@'
#!/bin/sh
value=$(sed -n 's/^timeout_ms[[:space:]]*=[[:space:]]*//p' service.conf)
test -n "$value" || exit 125
test "$value" -le 300
'@ | Set-Content check-regression.sh

@'
$line = Get-Content service.conf | Where-Object { $_ -match '^timeout_ms\s*=' } | Select-Object -First 1
if (-not $line) { exit 125 }
$value = [int](($line -split '=',2)[1].Trim())
if ($value -le 300) { exit 0 } else { exit 1 }
'@ | Set-Content check-regression.ps1

git add .
git commit -m "C1: establish known-good service and test oracle"
$C1 = git rev-parse HEAD

2. Generate the controlled history

Run the following on the chosen shell, editing files exactly as shown. Capture each OID because forensic work depends on exact objects, not remembered commit positions.

printf "# Service runbook\n" > RUNBOOK.md
git add RUNBOOK.md
git commit -m "C2: add service runbook"
C2=$(git rev-parse HEAD)

printf "FEATURE_FLAG=enabled\n" > feature.env
git add feature.env
git commit -m "C3: enable feature flag"
C3=$(git rev-parse HEAD)

# Change threshold=10 to threshold=20 in policy.txt.
git add policy.txt
git commit -m "C4: raise policy threshold"
C4=$(git rev-parse HEAD)

printf "retries=2\n" >> service.conf
git add service.conf
git commit -m "C5: add retry policy"
C5=$(git rev-parse HEAD)

# Regression: change timeout_ms=100 to timeout_ms=900.
git add service.conf
git commit -m "C6: increase timeout beyond service SLO"
C6=$(git rev-parse HEAD)

printf "incident_hint=timeout\n" >> RUNBOOK.md
git add RUNBOOK.md
git commit -m "C7: document timeout troubleshooting"
C7=$(git rev-parse HEAD)

# Formatting-only policy change: add spaces around '=' on every policy.txt line.
git add policy.txt
git commit -m "C8: format policy file"
C8=$(git rev-parse HEAD)

# Change mode=safe to mode=strict in service.conf.
git add service.conf
git commit -m "C9: enable strict mode"
C9=$(git rev-parse HEAD)

printf "metric=request_latency\n" >> service.conf
git add service.conf
git commit -m "C10: add latency metric"
C10=$(git rev-parse HEAD)

git branch release/1.x "$C5"
git log --graph --decorate --oneline --all

Known behavioral boundary: C1–C5 should pass the timeout oracle; C6–C10 should fail it.

3. Verify the endpoints before bisecting

Git Bash / Bash / zsh

git switch --detach "$C1"
sh check-regression.sh
echo "known-good exit=$?"

git switch --detach "$C10"
sh check-regression.sh
echo "known-bad exit=$?"

git switch trunk
git status --short --branch

PowerShell

git switch --detach $C1
powershell -NoProfile -ExecutionPolicy Bypass -File .\check-regression.ps1
"known-good exit=$LASTEXITCODE"

git switch --detach $C10
powershell -NoProfile -ExecutionPolicy Bypass -File .\check-regression.ps1
"known-bad exit=$LASTEXITCODE"

git switch trunk
git status --short --branch

The known-good exit must be 0. The known-bad exit must be non-zero but not 125. If these endpoint checks fail, stop—the oracle or boundary is wrong.

4. Perform a manual bisect and classify candidates from the oracle

git bisect start "$C10" "$C1"
git status --short --branch
git show --no-patch --oneline HEAD
sh check-regression.sh
echo $?

For each candidate, run the same oracle. If it exits 0, run git bisect good. If it exits 1 here, run git bisect bad. Repeat the inspect → test → classify loop until Git reports the first bad commit. Do not classify a commit by its message or date.

git bisect log
git bisect reset
git status --short --branch

reset returns to the pre-bisect branch/commit and removes normal bisect-session refs.

5. Run the deterministic oracle automatically

Git Bash / Bash / zsh

git bisect start "$C10" "$C1"
git bisect run sh check-regression.sh
CULPRIT=$(git rev-parse refs/bisect/bad)

git bisect log
git show --stat --oneline "$CULPRIT"
git diff "$CULPRIT^" "$CULPRIT" -- service.conf
git bisect reset

PowerShell

git bisect start $C10 $C1
git bisect run powershell -NoProfile -ExecutionPolicy Bypass -File .\check-regression.ps1
$CULPRIT = git rev-parse refs/bisect/bad

git bisect log
git show --stat --oneline $CULPRIT
git diff "$CULPRIT^" $CULPRIT -- service.conf
git bisect reset

Expected: C6 is reported as the first bad commit. The bisect result proves that, under this oracle and selected ancestry interval, C6 is the earliest tested commit on the searched path that exhibits the regression.

6. Understand the automated exit-code contract

Oracle exit Bisect meaning
0 good/old
1–127 except 125 bad/new
125 skip this revision as untestable
other aborting/error statuses stop the automated bisect

Do not use 125 for “probably bad.” It means the oracle cannot classify that revision.

7. Use blame to inspect current policy-line origin

git blame -L 2,2 -- policy.txt
git show --stat "$C8"
git show "$C8^..$C8" -- policy.txt

Because C8 reformatted the file, ordinary blame can attribute the current threshold line to C8 even though the logical threshold value was introduced at C4.

8. Ignore the known formatting revision during blame analysis

git blame --ignore-rev "$C8" -L 2,2 -- policy.txt
printf "%s\n" "$C8" > .git-blame-ignore-revs
git blame --ignore-revs-file .git-blame-ignore-revs -L 2,2 -- policy.txt

When Git can reattribute the line through the ignored formatting commit, the logical line points back toward C4. The ignore file must contain full object IDs in the documented format; it is analysis policy, not evidence that C8 “did not happen.”

9. Use -S to find when the bad value appeared

git log -S'900' --oneline -p -- service.conf
git show "$C6" -- service.conf

In this synthetic history, the count of literal 900 changes from zero to one at C6, so -S'900' independently points at the regression commit.

10. Show why -S'timeout_ms' and -G differ

git log -S'timeout_ms' --oneline -- service.conf
git log -G'^timeout_ms[[:space:]]*=' --oneline -- service.conf

-S'timeout_ms' primarily finds the commit that introduced or removed that token because its count stays one when 100 becomes 900. -G selects commits whose patch added/removed matching timeout lines, so it also sees value-changing patches.

11. Diverge a support line and calculate the merge base

git switch release/1.x
printf "support_note=1.x\n" >> service.conf
git add service.conf
git commit -m "R1: support-line note"
R1=$(git rev-parse HEAD)

git switch trunk
BASE=$(git merge-base trunk release/1.x)
git show --no-patch --oneline "$BASE"
git merge-base --is-ancestor "$BASE" trunk
git merge-base --is-ancestor "$BASE" release/1.x

git diff "$BASE" trunk -- service.conf
git diff "$BASE" release/1.x -- service.conf

The merge base should be C5—the branch point before the regression. Therefore the support line does not inherit C6 merely because its latest commit may have a later wall-clock timestamp.

12. Challenge — choose the forensic tool from the question

  1. You can reproduce a regression at C10 and know C1 is good. Which tool narrows the first behavioral failure?
  2. You want the commit where literal 900 first appeared. Which pickaxe form?
  3. You want every patch that modified a line matching timeout_ms=..., even if the token count stayed one. Which form?
  4. Blame points at a mechanical formatting commit. Which blame option can test attribution while ignoring that commit?
  5. You want to compare trunk and release/1.x from their shared divergence point. Which command finds that point?

13. Cleanup

Save any bisect log/evidence before deleting the disposable repository.

Git Bash / Bash / zsh

git bisect reset 2>/dev/null || true
cd ../..
pwd
rm -rf git-forensics-lab

PowerShell

git bisect reset 2>$null
Set-Location ../..
Get-Location
Remove-Item -Recurse -Force git-forensics-lab

14. Knowledge check

Question 1. Why test both known-good and known-bad endpoints before starting?

Question 2. What does automated bisect exit code 125 mean?

Question 3. Why can ordinary blame point to C8 instead of C4?

Question 4. Why can -G see the timeout value change when -S'timeout_ms' does not?

Question 5. What does merge-base=C5 tell you about release/1.x?

15. Summary

You verified endpoints, manually and automatically bisected a deterministic regression, interpreted the bisect exit contract, distinguished line-origin from logical change with blame ignore-revision analysis, used both pickaxe modes, and tied support-line comparison to a graph-derived merge base.

Next

Turn techniques into team forensic policy

Lesson 3 defines ignore-revision governance, oracle design, history-retention requirements, clock limitations, and incident-evidence handling.

Authoritative references

 git-bisect
 git-blame
 git-log
 git-merge-base

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.