Git Bisect and Blame

Find the exact commit that broke something with git bisect, and see who last changed each line with git blame.

Lesson 16 of 30Advanced17 min read

"It worked last month." When something breaks and nobody knows why, the fastest route to a fix is often finding which change caused it. Once you know the exact commit, the bug is usually obvious: it's somewhere in the few lines that commit changed.

Searching through hundreds of commits by hand would take hours. git bisect does it in minutes using binary search: it checks out the commit halfway between a known good version and a known bad one, you say whether that commit is good or bad, and Git halves the search again. Even a thousand commits need only about ten checks.

In this lesson you'll run a bisect by hand and automatically with a test script, then meet two more detective tools: git blame, which shows who last changed each line of a file and in which commit, and git log -S, which finds the commits that added or removed a piece of text.

How bisect works

You need two facts to start: a commit where the bug exists (usually the current one) and an older commit where it didn't:

Terminal
git bisect start
git bisect bad                 # the current commit is broken
git bisect good v1.0.0         # this release worked

Git checks out a commit roughly halfway between them. Test it, by running the program or opening the page, and report what you see:

Terminal
git bisect good    # or: git bisect bad

Each answer halves the remaining range. When only one candidate is left, Git prints "is the first bad commit" along with its details. Finish with git bisect reset, which returns you to the branch you started on.

Automating bisect

If you can check for the bug with a command, Git can run the whole search for you:

Terminal
git bisect start HEAD v1.0.0
git bisect run npm test

git bisect run executes the command at each step. An exit code of 0 means good, 1 to 127 (except 125) means bad, and 125 means "can't test this commit, skip it". A short test script that checks just the broken behaviour is ideal. On big projects, automated bisects often find the culprit while you make a cup of tea.

git blame

git blame shows, for every line of a file, the commit, author and date of the last change to that line:

Terminal
git blame -L 10,20 app.js
  • -L 10,20 limits the output to lines 10 to 20.
  • -w ignores whitespace-only changes, so reformatting doesn't hide the real author.
  • Editors such as VS Code (with GitLens) and GitHub's Blame view show the same information beside the code.

Despite the name, blame is about understanding, not blaming people: the commit message and its pull request usually explain why a line was written that way.

Searching history with log -S and -G

To find when a piece of text was added or removed anywhere in the project, use the pickaxe option:

Terminal
git log -S "calculateTax" --oneline
git log -G "price\s*\*\s*0\.2" --oneline

-S lists commits that changed the number of times the text appears: the commit that introduced a function and the one that deleted it. -G takes a regular expression and finds commits whose changes match it. Add -p to see the actual changes, or -- path/to/file to search one file.

Git Bisect Syntax

Investigation commands

git bisect start
git bisect bad [commit]
git bisect good <commit>
git bisect run <test-command>
git bisect reset

git blame -L 10,20 -w <file>
git log -S "text" --oneline
git log -G "regex" -p -- <file>

Git Bisect Summary

  • git bisect uses binary search between a good and a bad commit to find where a bug started.
  • Mark each tested commit with git bisect good or bad; finish with git bisect reset.
  • git bisect run <command> automates the search using the command's exit code.
  • git blame shows the last commit, author and date for each line of a file.
  • git log -S "text" and -G "regex" find commits that added or removed specific code.

Git Bisect Examples

Each example shows the code, the output it produces and what each part does.

Example 1

A manual bisect

The checkout page is broken now, but worked at tag v1.0.0, 12 commits ago.

Terminal
git bisect start
git bisect bad
git bisect good v1.0.0
# test the checked-out version: works
git bisect good
# test: broken
git bisect bad
# test: works
git bisect good
# test: broken
git bisect bad
git bisect reset
Output
Bisecting: 5 revisions left to test after this (roughly 3 steps)
[a41f9e2] Add newsletter signup
Bisecting: 2 revisions left to test after this (roughly 2 steps)
[d77c013] Refactor price formatting
Bisecting: 0 revisions left to test after this (roughly 1 step)
[b52e8a1] Round prices in basket
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[e9c3f40] Move tax calculation to helper
e9c3f40 is the first bad commit
commit e9c3f40
Author: Ben Okafor <ben@example.com>
    Move tax calculation to helper
Previous HEAD position was e9c3f40... Switched to branch 'main'
Line-by-line explanation
  1. Lines 1 to 3Start, and mark the current commit bad and the v1.0.0 tag good.
  2. Lines 4 to 11Four tests narrow 12 commits down to one.
  3. Line 12reset returns you to your branch.
Example 2

Automating the search with a test script

check-total.sh exits with 0 if the basket total is correct and 1 if it isn't.

Terminal
git bisect start HEAD v1.0.0
git bisect run ./check-total.sh
git bisect reset
Output
running './check-total.sh'
Bisecting: 2 revisions left to test after this (roughly 2 steps)
running './check-total.sh'
Bisecting: 0 revisions left to test after this (roughly 1 step)
running './check-total.sh'
e9c3f40 is the first bad commit
bisect found first bad commit
Line-by-line explanation
  1. Line 1Give the bad and good commits directly to bisect start.
  2. Line 2Git runs the script at every step and reads its exit code.
  3. Line 3Always reset when you're done.
Example 3

Who changed this line, and when was it added?

Terminal
git blame -L 3,4 basket.js
git log -S "TAX_RATE" --oneline
Output
e9c3f40 (Ben Okafor 2024-08-20 3) const TAX_RATE = 0.02;
2d6f1e4 (Sam Lee    2024-07-11 4) function total(items) {
e9c3f40 Move tax calculation to helper
1a2b3c4 Add tax to basket total
Line-by-line explanation
  1. Line 1Line 3 was last changed in e9c3f40, the same commit bisect found: the rate was typed as 0.02 instead of 0.2.
  2. Line 2-S lists every commit where TAX_RATE was added or removed, newest first.

Common Git Bisect Mistakes

Forgetting to reset after bisecting

Don't
# bisect found the bug... then you start working
git commit -am "Fix tax"     # committed in detached HEAD on an old commit
Do
git bisect reset
git switch -c fix-tax-rate
git commit -am "Fix tax rate typo"

Marking a commit you couldn't test

Don't
# this version doesn't even build, so you guess
git bisect good
Do
git bisect skip

A wrong guess sends the search in the wrong direction; skip lets Git choose a nearby commit.

Using blame to point fingers

Don't
# "Ben broke it!"
Do
git show e9c3f40          # read the change and its message, then fix it together

Git Bisect in Real Projects

Bisect in the real world

Large projects such as the Linux kernel and web browsers use git bisect run with automated tests to track down regressions among thousands of commits, and continuous integration services can run a bisect automatically when a test starts failing. Day to day, git blame in the editor is one of the most-used ways developers learn why code looks the way it does.

Git Bisect FAQ

Can I start bisect with the bad and good commits in one command?

Yes: git bisect start <bad> <good>, for example git bisect start HEAD v1.2.0. Git checks out the first commit to test straight away. It saves typing git bisect bad and git bisect good separately.

Can I use words other than good and bad in git bisect?

Yes, which helps when you're looking for when something improved rather than broke. old and new are built in, or define your own: git bisect start --term-old=fast --term-new=slow. You then mark commits with git bisect fast and git bisect slow.

How many steps does git bisect take?

Each step halves the number of commits left, so it takes roughly log₂ of the number of commits. About 1,000 commits need around 10 tests, and 100,000 need around 17. Skipped commits can add a few extra steps.

How do I save or repeat a bisect session?

git bisect log prints every command of the current session. Save it to a file, edit out a wrong answer if you made one, then run git bisect reset and git bisect replay that-file to get back to the same point. It's the easy way to fix a mistaken good or bad mark.

How do I stop git blame showing formatting commits?

git blame -w ignores whitespace changes. For bigger reformatting commits, list their hashes in a file and run git blame --ignore-revs-file .git-blame-ignore-revs file.js, or set blame.ignoreRevsFile so it applies every time. Blame then looks past those commits to the change before them.

Git Bisect Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Fill in the blankseasy

Complete the commands that begin a bisect with the current commit marked as broken.

git bisect 
git bisect 
Multiple choiceeasy

What search method does git bisect use?

Predict the outputmedium

About how many tests does bisect need to search 1,000 commits?

Multiple choicemedium

In git bisect run, what exit code means "skip this commit"?

Multiple choicehard

Which command finds commits that added or removed the text "sendEmail"?

Git Bisect Quiz

5 questions to check what you learned.

  1. What two pieces of information does bisect need to start?

    • The author's name and email
    • A commit with the bug and an older commit without it
    • The branch name only
    • A remote URL
  2. What does git bisect reset do?

    • Deletes the bad commit
    • Ends the bisect and returns to the branch you started on
    • Restarts the search
    • Resets your files to the bad commit
  3. What does git blame show?

    • Who has access to the repository
    • Failed tests
    • The last commit, author and date that changed each line of a file
    • Deleted branches
  4. What does the -w option do with git blame?

    • Ignores whitespace-only changes
    • Shows warnings
    • Writes the output to a file
    • Shows the whole file history
  5. Why do small commits make bisect more useful?

    • Bisect only works with small commits
    • Small commits are faster to check out
    • They can be skipped automatically
    • The commit bisect finds contains only a few changes, so the bug is easy to spot