Bisect and blame
Expert
Why this matters
"It worked last month" is a common report and a bad starting point. Bisect turns it into a specific commit in about ten checks, even across a thousand commits, and the searches after it answer the same question for a line of text.
Bisect
It is a binary search over history. You tell Git one commit where the problem exists and one where it does not; Git checks out the midpoint and asks; each answer halves the range.
$ git bisect start <bad> <good>Then, at each step, git bisect good or git bisect bad. Ten steps cover a thousand commits.
Automating it
If the check can be a script that exits zero for good and non-zero for bad, Git does the whole search:
$ git bisect start HEAD HEAD~6
$ git bisect run ./check.sh1e90067a1457fd9969319c1a97c12bedde7f7ea7 is the first bad commit$ git bisect reset
The script can be anything: a test suite, a build, a grep for a string that should not be there. For a documentation problem it might check that a link resolves or that a page builds.
Two rules make this work:
- The script must not depend on uncommitted state, since Git checks out old commits underneath it.
git bisect resetat the end, which returns you to the branch you started from. Forgetting it leaves you detached (lesson 12.5).
git bisect skip handles a commit that cannot be tested, for example one that does not build.
Blame
$ git blame -L 1,4 docs/faq.mdb81702c5 (You 2026-09-02 09:15:00 +0200 1) # Frequently asked questions
b81702c5 (You 2026-09-02 09:15:00 +0200 2)
b81702c5 (You 2026-09-02 09:15:00 +0200 3) ## Is Trailguide free?
b81702c5 (You 2026-09-02 09:15:00 +0200 4) -L 1,4 limits it to a range of lines, which is what makes blame usable on a long file. Two options matter beyond that:
| Option | Does |
|---|---|
-w |
Ignore whitespace changes, so a reformatting commit stops hiding the real author |
-C |
Detect lines moved or copied from another file |
--since=1.year |
Stop looking further back |
Blame's weakness is that it reports the last commit to touch a line, which is often a reformatting or a rename. -w and -C are how you see past that, and the platform's blame views have an "ignore this commit" control for the same reason (lesson 10.4).
Two searches that beat blame
When did this string appear or disappear?
$ git log --oneline -S 'BROKEN' -- data/trails.csv1e90067 data: add row 4-S finds commits that changed the number of occurrences of a string: exactly the question "who added this?". -G takes a regular expression and finds commits whose diff matches it.
How did these lines get like this?
$ git log -L 1,3:docs/faq.mdThat prints the history of those specific lines, with each diff that touched them, following the range as it moves. It is slower than blame and answers a better question, because it shows the sequence rather than only the last step.
Choosing
| Question | Tool |
|---|---|
| When did this behaviour break? | git bisect |
| Who last changed this line, and why? | git blame -L |
| Which commit added this string? | git log -S |
| How did these lines evolve? | git log -L |
| What changed in this file over time? | git log -p -- <file> |
How to do it
$ git bisect start <bad> <good>
$ git bisect run ./check.sh
$ git bisect reset
$ git blame -w -C -L 20,40 <file>
$ git log -S '<string>' -- <path>
$ git log -L 20,40:<file>No bisect interface. Blame comes from the GitLens extension or the Timeline view (lesson 15.10); the searches are terminal work.
Annotate with Git Blame in the gutter, with options for whitespace and previous revisions, and the Log's filters cover much of log -S. Bisect has no interface here either.
A file's Blame view shows the same information with links to each commit and its merge request, which is often the fastest route from a line to the discussion that produced it (lesson 9.4).
The Blame button on a file, with a control to ignore a chosen commit, which is the platform's version of -w for a formatting change.
Common mistakes
- Forgetting
git bisect reset, leaving the repository detached. - A bisect script that depends on uncommitted files.
- Trusting blame's last-toucher when a reformatting commit sits on top. Use
-wand-C. - Blaming a whole file rather than a line range, and drowning.
- Using blame to answer "who added this string?" when
log -Sanswers it directly.
Try it yourself
Goal: find a deliberately broken commit automatically.
- In a practice repository, make six commits, one of which adds the line
BROKENto a file. - Write a script that exits 1 if the file contains
BROKENand 0 otherwise, and make it executable. - Run
git bisect start HEAD HEAD~6thengit bisect run ./check.sh. - Read the "is the first bad commit" line and confirm it is the one you expect.
- Run
git bisect reset, then find the same commit withgit log -S 'BROKEN'.
Expected result: the same commit found two ways, one by binary search over behaviour and one by searching the content of the diffs.
Show solution
Step 5 is the comparison. log -S is faster when you know the exact string that appeared; bisect is the tool when you only know that behaviour changed. Most real investigations start with the second and end with the first.