Find which commit introduced a bug
Use `git bisect` to binary-search the history between a known-good and known-bad commit, narrowing thousands of commits to the single guilty one in log₂(N) steps.
How to find which commit introduced a bug in each shell
git bisect start && git bisect bad HEAD && git bisect good v1.0`git bisect start` enters bisect mode (sets `.git/BISECT_*` state files). Mark the current tip BAD (or a specific SHA) and the known-good ref. Git checks out the midpoint commit; you test, then mark `git bisect bad` or `git bisect good`. Halt with `git bisect reset` to return to your original branch.
git bisect start && git bisect bad HEAD && git bisect good v1.0git bisect start; and git bisect bad HEAD; and git bisect good v1.0Fish uses `; and` (or `; or`) for short-circuit chaining instead of `&&`/`||` — though fish 3.4+ ALSO accepts `&&`. Both forms work. The git bisect state files are shell-agnostic; switching shells mid-bisect is safe.
git bisect start; if ($LASTEXITCODE -eq 0) { git bisect bad HEAD; git bisect good v1.0 }pwsh 5.1 has no native `&&` — pwsh 7+ does. The portable form chains via `$LASTEXITCODE` checks. For the automated form, `git bisect run` invokes your script regardless of shell, so `git bisect run pwsh -File test.ps1` works from any shell. PowerShell exit codes: end your test script with `exit 0` (good) / `exit 1` (bad) / `exit 125` (skip).
git bisect start && git bisect bad HEAD && git bisect good v1.0cmd accepts `&&` natively (it's a cmd.exe operator, predates bash). For `git bisect run`, point at a `.cmd`/`.bat` file: `git bisect run test.cmd`. Inside batch, `exit /b 0` / `exit /b 1` / `exit /b 125` set the rc git inspects.
Equivalents listed for Bash, Zsh, Fish, PowerShell, cmd.exe.
Gotchas & notes
- **Binary search means log₂(N) steps, not N.** A 1024-commit history takes 10 bisect steps to pinpoint the guilty commit; 100k commits takes 17 steps. Each step git checks out the midpoint, you reproduce (or fail to reproduce) the bug, then `git bisect bad` or `git bisect good`. The decisive ROI move is `git bisect run <test-cmd>` — git invokes the test at each step automatically and walks the bisect to completion in seconds: `git bisect run npm test`, `git bisect run cargo test --test the_failing_test`, `git bisect run ./scripts/reproduce-bug.sh`. The test must be DETERMINISTIC — flaky tests make bisect converge on the wrong commit.
- **Exit-code contract for `bisect run`: 0 = good, non-zero = bad, 125 = skip.** This is git's convention, not a shell convention — applies identically across bash, fish, pwsh, cmd. Use 125 when the commit IS UNTESTABLE (compile error, missing dependency, infrastructure broken on that historic commit) — git treats it as "neither evidence" and tries an adjacent commit. NEVER return 125 for "I think it might be bad" — that's what bad is for. CAVEAT: shell scripts that fail with rc 127 (command not found) get treated as BAD, which can mask infrastructure breakage. Wrap with `command -v foo || exit 125` defensively.
- **When the bug is the absence of a feature, invert good/bad.** `git bisect terms <new-bad> <new-good>` renames the labels — e.g., to find the commit that FIXED a bug (where HEAD works and an old ref didn't), `git bisect terms fixed broken` then `git bisect fixed HEAD; git bisect broken v1.0`. Same binary search, inverted semantics. Useful for "which commit added this feature?" hunting too: bad = "doesn't have the feature", good = "has it"; bisect converges on the introducing commit.
- **Worktree isolation keeps bisect from disrupting other work.** `git worktree add ../bisect HEAD && cd ../bisect && git bisect start ...` runs the entire bisect in a separate checkout — your main worktree stays on your feature branch, your IDE keeps pointing at HEAD. Removes the "argh, bisect checked out 3 weeks ago and now my IDE indexed everything" friction. Tear down with `git worktree remove ../bisect` after `git bisect reset`. On Windows, worktrees work but symlinks inside the worktree directory may require admin rights.
- **Merge commits complicate things; `--first-parent` simplifies.** When the bug was introduced by a merge (the merge itself is clean, but it brought in a contaminated branch), bisect can descend into either parent. By default it picks the SHORTER side. Restrict to main-line commits only with `git bisect start --first-parent` (git 2.29+, Oct 2020) — bisect stays on the "what merged into main" timeline, ignoring the per-feature-branch internals. Useful for repos with squash-merges (where each PR is one commit on main): `--first-parent` makes bisect efficient even on 10k-PR histories.
Related commands
Related tasks
- Show git commit history as a graph— Render the commit DAG as an ASCII graph so branch topology — merges, forks, fast-forwards, and orphan tips — is visible at a glance, instead of the default linear `git log` that hides every parallel branch.
- Show diff between two git branches— Compare two branches' code — for code review, "what did this feature change?" inspection, or merge-conflict preview. The two-dot vs three-dot syntax distinction silently changes what the diff INCLUDES.
- List modified files in a git repo— Print the paths of files with uncommitted changes (staged or unstaged) — for pre-commit hooks, CI lint-only-changed gates, and "what am I about to commit".