Find the First Bad Commit with git bisect
You will finish with the commit that first introduced a regression, rather than a long list of plausible changes. Git bisect performs a binary search: you identify one known-good commit and one known-bad commit, test the midpoint, then classify it so Git can choose the next one. Allow 10 to 30 minutes for the session itself, plus the time needed to build and test the project.
The route
Jump straight to the step you need, or tick off Done means at the end.
You need a clean working tree, a reproducible test, and Git 2.43.0 or later for the commands shown here. The installed package is git-man version 2.43.0-1ubuntu7.3 and the local manual identifies Git 2.43.0. The upstream manual has since added git bisect next; this guide keeps to commands available in the installed executable and calls out the version boundary where it matters.
1. Establish trustworthy endpoints
First confirm that the current checkout is really the broken case and find a commit where the same test passed. Use a tag, branch, or full commit ID for the endpoints. The labels below are placeholders, so replace them with revisions from your repository:
$ git status --short
$ git log --oneline --decorate -n 12
$ git tag --list 'v*' --sort=-version:refname | head
$ git show --stat --oneline <known-good-commit>
$ git show --stat --oneline <known-bad-commit>
An empty result from git status --short is the useful result. Bisect checks out different commits, so uncommitted edits can be lost or can change the test's result. Save work in a commit or stash it before continuing. Do not use git reset --hard merely to make the status clean unless you have deliberately preserved anything you need: that command discards tracked working-tree changes.
Checkpoint: you should be able to state precisely what the test means. For a regression, the known-good revision passes and the known-bad revision fails. If either endpoint is uncertain, bisect can still produce an answer, but the answer will not be useful.
2. Start a manual bisection
Pass the bad revision first and the good revision second. The following form also ends the revision arguments with --, which prevents a path from being confused with a revision when you later add path limits:
$ git bisect start <known-bad-commit> <known-good-commit> --
Bisecting: 12 revisions left to test after this (roughly 4 steps)
Git checks out a midpoint and records the bisection state. Build the checked-out source and run the same test you used for the endpoints. Mark the current revision according to the result:
$ ./configure && make
$ ./run-regression-test
$ git bisect good
Bisecting: 6 revisions left to test after this (roughly 3 steps)
$ ./run-regression-test
$ git bisect bad
Do not copy the build commands blindly. Substitute the project's real build and test commands, and keep the test input and environment consistent. After every good or bad, Git normally checks out the next candidate automatically. Repeat until it reports the first bad commit. The result is also available through refs/bisect/bad.
Checkpoint: inspect the answer before acting on it:
$ git show --stat --summary refs/bisect/bad
$ git bisect log
The log records the endpoint and each classification. It is useful evidence when you need to explain why a change was blamed, and it gives you something to edit if you later discover a mistaken result.
3. Automate a repeatable test
Manual testing is often enough, but a script makes the classifications consistent. Keep the script outside the repository, so checking out older commits cannot replace or modify the test itself. This example treats a passing test as good and a failing test as bad:
#!/bin/sh
set -u
if ! make; then
exit 125
fi
./run-regression-test
Save it as /tmp/check-regression.sh or another protected location and make it executable. The script must return 0 for good or old code. A status from 1 through 127 means bad or new, except 125. Status 125 means that this revision cannot be tested and tells bisect to skip it. Any other status aborts the automated session, so do not let a missing command or an infrastructure failure look like a product regression.
$ chmod 700 /tmp/check-regression.sh
$ git bisect start <known-bad-commit> <known-good-commit> --
$ git bisect run /tmp/check-regression.sh
<commit-id> is the first bad commit
The command may run the build and test several times. It changes the checkout during the run, so do not edit files in the worktree while it is active. If a build needs a temporary workaround, the script must apply it and then restore a pristine tree before returning. Treat commands such as git reset --hard inside such a script as destructive: they remove tracked edits made during that iteration.
4. Deal with commits that cannot be tested
A midpoint may fail to build for an unrelated reason, depend on a missing tool, or be outside the range your test understands. Use git bisect skip for that revision instead of guessing:
$ git bisect skip
Bisecting: 5 revisions left to test after this (roughly 3 steps)
You can skip a revision range with a revision expression such as git bisect skip <older>..<newer>. That expression excludes commits after the first endpoint up to and including the second. Add the first endpoint separately when it must also be skipped:
$ git bisect skip <older> <older>..<newer>
Skipping a commit beside the actual change can leave Git unable to identify one exact first bad commit. The final result may be a set of candidates rather than a single answer. Record why each revision was skipped, then test the remaining candidates directly.
5. Narrow or correct the search
If the problem can only involve certain paths, supply them after -- when starting the session:
$ git bisect reset
$ git bisect start <known-bad-commit> <known-good-commit> -- src tests
You can also give more than one known-good revision after the bad revision. This can reduce the search space when those good points are independent boundaries. For merge-heavy history, the installed manual supports --first-parent; it follows only the first parent at merges, which can attribute a regression to the merge rather than to an unbuildable commit inside the merged branch:
$ git bisect start --first-parent <known-bad-commit> <known-good-commit> --
Use that option only when the first-parent history matches the question you are asking. It deliberately ignores other parent lines.
If you marked a revision incorrectly, save the current record, edit out the wrong entries, reset the session, and replay the corrected file:
$ git bisect log > /tmp/bisect-log.txt
$ editor /tmp/bisect-log.txt
$ git bisect reset
$ git bisect replay /tmp/bisect-log.txt
Review the replay output before continuing. The temporary log contains revision information, so protect it if repository history is sensitive.
6. Clean up and return to your branch
When you have recorded the result, end the session explicitly:
$ git bisect reset
Previous HEAD position was <commit-id> candidate
Switched to branch 'main'
$ git status --short
With no argument, git bisect reset returns to the checkout that was active before git bisect start. Use git bisect reset refs/bisect/bad if you want to inspect the first bad revision after finishing, or git bisect reset HEAD to remain on the current checkout. The reset removes the bisect state; it does not repair the underlying bug or create a commit.
For a broader change, you can replace the good and bad vocabulary with old and new, or custom terms such as broken and fixed. Do not mix the two vocabularies in one session. Check the active terms with git bisect terms.
Done means
- The good endpoint passes and the bad endpoint fails under the same test conditions.
- The bisect result was checked with
git showand its reasoning is recorded bygit bisect log. - Unbuildable or untestable revisions were skipped explicitly, with their effect understood.
- An automated script returns 0 for good, a permitted non-zero status for bad, and 125 only for untestable revisions.
git bisect resethas returned the worktree to the original branch andgit status --shortis as expected.