Why blame
Blame answers "who wrote this line and why?" - the gateway to digging through history when something seems wrong, suspicious, or surprisingly clever. The name is misleading; the goal is rarely to assign fault, more often to find context.
The basics
git blame src/checkout.js
git blame -L 40,60 src/checkout.js # specific line range
Each line shows the SHA, author, date, and content.
Following moved code
Code often moves between files. Default blame stops at the last move. To follow further:
git blame -M src/checkout.js # follow within-file moves
git blame -C src/checkout.js # follow across-file moves
git blame -CCC src/checkout.js # aggressive cross-file detection
Each C increases sensitivity. Three Cs catches even small fragments moved between files - useful, slow.
Ignoring noise commits
Mass-format commits (prettier, black, lint autofix) bury the real history. .git-blame-ignore-revs lists SHAs to skip:
# .git-blame-ignore-revs
abc1234567890 # Apply prettier across codebase
def4567890123 # Apply black to all Python
git blame --ignore-revs-file=.git-blame-ignore-revs src/checkout.js
# Or set as default:
git config blame.ignoreRevsFile .git-blame-ignore-revs
GitHub honours this file natively.
Blaming by content
To find when a particular string was introduced:
git log -S 'EMAIL_REGEX' -- src/
git log -G 'email.*regex' --pickaxe-regex -- src/
-S finds commits that change the count of a string. -G finds commits that touch a regex match. Both are blame-shaped tools at the commit level.
Hunting a regression
Combine blame with bisect for full archaeology:
git blame -L 40,60 src/checkout.js
# find suspicious SHA
git show <sha>
git log --follow -p src/checkout.js
git bisect start
git bisect bad HEAD
git bisect good <earlier-sha>
Annotated views in editors
VS Code's GitLens, JetBrains' built-in annotate, and Vim's fugitive.vim offer interactive blame. Click a line, see the commit, navigate back through history.
Reverse blame
git log --reverse traces forward in time:
git log --reverse --follow -p -L 40,60:src/checkout.js
This shows each change to lines 40-60 chronologically - useful when you want to see the evolution of a function from inception.
Blame without history
Sometimes you just want to know who last modified a line, without metadata noise:
git blame --line-porcelain src/checkout.js | \
sed -n 's/^author //p' | sort | uniq -c | sort -rn
This counts authors of every line - a quick "who owns this file?" report.
Etiquette
Blame is for understanding, not finger-pointing. The author of a line might have written it five years and three jobs ago. Use blame to ask about the change ("I see you added this regex - was email validation always this strict?"), not to assign fault.
Long-term maintenance
Maintain .git-blame-ignore-revs as you go. Whenever you do a mass reformat, add the SHA. Future archaeologists will thank you.