Home DevOps Git Blame — Wrong Developer Got Flagged in Postmortem
Intermediate 4 min · July 11, 2026
Git Blame: Find Who Changed What and When

Git Blame — Wrong Developer Got Flagged in Postmortem

git blame showed Alice's name on a buggy line.

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Everything here is grounded in real deployments.

Follow
Production
production tested
July 12, 2026
last updated
2,406
articles · all by Naren
Before you start⏱ 15 min
  • Git 2.x installed, basic familiarity with the command line, a Git repository with multiple commits, understanding of commits and branches.
 ● Production Incident
Quick Answer

git blame shows every line with commit hash, author, date. git blame -L 10,20 narrows to a range. Use git blame -w to ignore whitespace changes.

✦ Definition~90s read
What is Git Blame?

git blame annotates each line of a file with the commit SHA, author, and timestamp of the last modification. It answers: who last touched this line, and in which commit?

Imagine a whiteboard where each word has a sticky note showing who last wrote it and when.
Plain-English First

Imagine a whiteboard where each word has a sticky note showing who last wrote it and when. git blame is those sticky notes. It doesn't tell you who “owns” the code or who originally wrote it — it tells you who last changed each character, even if that was a merge or a formatting fix.

Teams use git blame to find who introduced a bug. But blame shows the LAST person to touch a line, not the ORIGINAL author. A formatting commit, a merge conflict resolution, or a whitespace fix all overwrite the blame trail. In the worst cases, the blame finger points at the wrong developer entirely. This guide covers how to read blame accurately, how to dig past cosmetic changes to find the real origin, and the production incident where blame misattribution nearly cost an engineer their reputation.

What Git Blame Actually Does

Git blame annotates each line of a file with metadata: the commit hash, author, date, and line number. It answers the question: "Who last touched this line, and in which commit?" This is not a forensic tool for assigning blame in a negative sense; it's a breadcrumb trail for understanding code evolution. When you run git blame on a file, you get a line-by-line breakdown. The output shows the commit SHA, author, timestamp, and line content. For example, git blame -L 10,20 src/app.js limits the output to lines 10 through 20. This is invaluable when you encounter a confusing line of code and need to understand its origin. However, remember that blame only shows the last modification. A line might have been moved or reformatted by a later commit, obscuring the original author's intent. Always check the full commit context before drawing conclusions.

terminalBASH
1
2
cd /path/to/repo
git blame -L 50,60 config/database.yml
Output
a1b2c3d4 (Jane Doe 2026-06-01 14:22:10 +0000 50) production:
e5f6g7h8 (John Smith 2026-05-20 09:15:30 +0000 51) adapter: postgresql
a1b2c3d4 (Jane Doe 2026-06-01 14:22:10 +0000 52) database: myapp_production
i9j0k1l2 (Alice Lee 2026-04-10 11:00:00 +0000 53) pool: 25
🔥Blame is not a witch hunt
Use blame to understand context, not to point fingers. A line changed by a junior dev might have been forced by a code review from a senior. Always check the commit message and diff.
📊 Production Insight
In production incidents, blame helps identify when a bug was introduced. But beware: a line may have been changed by a whitespace fix, not the actual logic change. Always inspect the full diff.
🎯 Key Takeaway
Git blame shows the last commit that modified each line, helping trace code origins.
git-blame THECODEFORGE.IO Git Blame Data Layers Hierarchy of components involved in blame analysis User Interface CLI Command | IDE Plugin | Web UI Blame Engine Line Annotation | Commit Walker | Diff Parser Performance Optimizer File Size Check | Cache Layer | Parallel Processing History Resolver Branch Mapper | Merge Base Finder | Whitespace Filter Output Formatter Author Extractor | Timestamp Parser | Color Coder THECODEFORGE.IO
thecodeforge.io
Git Blame

Reading Blame Output Like a Pro

The default blame output is dense. Each line starts with a commit hash (abbreviated), author name, timestamp, line number, and then the code. The hash is clickable in most terminals if you have a git browser configured. The author is the committer, not necessarily the original author (though usually the same). The timestamp is in ISO 8601 format with timezone. To make it more readable, use options like --date=short for YYYY-MM-DD or --date=relative for "3 weeks ago". You can also filter by author: git blame --author=Jane file. If you want to see the full commit hash, use --abbrev=40. For large files, pipe to less or use -L to focus on a region. Pro tip: combine with git log on the commit hash to see the full commit message and diff. This workflow is standard for code reviews and debugging.

terminalBASH
1
git blame --date=short -L 1,10 README.md
Output
a1b2c3d4 (Jane Doe 2026-06-01) 1) # My Project
e5f6g7h8 (John Smith 2026-05-20) 2) ## Installation
a1b2c3d4 (Jane Doe 2026-06-01) 3) Run `npm install`.
💡Use --date=relative for quick context
When debugging, --date=relative shows how recent a change was. A line changed "2 days ago" is more suspicious than one changed "2 years ago".
📊 Production Insight
During an outage, I once traced a null pointer exception to a line blamed on a commit that only added a comment. The real change was in a previous commit that the blame didn't show because the line was moved. Always verify with git log -p on the file.
🎯 Key Takeaway
Customize blame output with date formats and line ranges to quickly find relevant changes.

Ignoring Whitespace and Moves with Git Blame

By default, blame considers any change to a line, including whitespace. This can be misleading when a file is reformatted (e.g., tabs to spaces) or when code is moved within the file. Use -w to ignore whitespace changes. For moved code, use --ignore-rev to skip a specific commit that only moved lines. You can also use --ignore-revs-file to list multiple commits. This is critical in projects that underwent a formatting overhaul. For example, if a commit changed all indentation from tabs to spaces, blame will show that commit for every line. Using -w will revert to showing the original author. Similarly, if a function was moved to a new file, blame on the new file will show the move commit. To find the original author, you need to blame the original file before the move.

terminalBASH
1
2
3
git blame -w src/app.js
# Or ignore a specific commit that only reformatted
git blame --ignore-rev abc1234 src/app.js
Output
e5f6g7h8 (John Smith 2026-05-20) 10) function foo() {
a1b2c3d4 (Jane Doe 2026-06-01) 11) return bar;
}
⚠ Ignore revs with caution
If you ignore a commit that also changed logic, you'll lose the true author. Only ignore commits that are purely cosmetic (e.g., formatting, moving).
📊 Production Insight
After a team-wide Prettier adoption, every line blamed the formatting commit. We added that commit to .git-blame-ignore-revs and configured git blame to use it by default. This restored meaningful blame output.
🎯 Key Takeaway
Use -w and --ignore-rev to see past formatting changes and find the original author of logic.
Git Blame vs Git Log for Tracing Trade-offs between blame and log for postmortem analysis Git Blame Git Log Granularity Line-level per file Commit-level across repo Performance on Large Files Slower, may need -L range Faster with path filters Whitespace Handling Supports -w flag No direct whitespace ignore Original Author Detection Shows last modifier only Can trace full history CI/CD Integration Complex to automate Easier with git log --since Common Pitfall Blames reformatting commits Misses line-level changes THECODEFORGE.IO
thecodeforge.io
Git Blame

Blame in Large Files: Performance and Strategies

Blame on a large file (e.g., 10k+ lines) can be slow because git walks the entire history for each line. To speed it up, limit the range with -L. If you need to blame the whole file, consider using git blame --porcelain for machine-readable output that is faster to parse. Another strategy: if you only care about recent changes, use --since to ignore commits older than a date. For monorepos, blame can be particularly heavy. Use git blame --progress to see progress. If you find yourself blaming the same file often, consider splitting it into smaller modules. In production, we once had a 15k-line configuration file that took 30 seconds to blame. We reduced it by breaking it into per-environment files.

terminalBASH
1
2
3
4
5
6
# Blame only lines 100-200
git blame -L 100,200 hugefile.js
# Blame with progress indicator
git blame --progress hugefile.js
# Blame only commits after a date
git blame --since="2026-01-01" hugefile.js
Output
Progress: 15% (1500/10000 lines)
💡Use --since to ignore ancient history
If you're investigating a recent bug, limit blame to the last few months. This drastically speeds up the command.
📊 Production Insight
In a monorepo with a 50k-line file, blame was unusable. We added a CI check that warns when files exceed 2k lines. This improved both blame performance and code maintainability.
🎯 Key Takeaway
Optimize blame performance by narrowing line ranges, using date filters, or splitting large files.

Blame Across Branches and Forks

By default, blame works on the current branch. To blame a file on a different branch, specify the branch name: git blame other-branch file. This is useful when comparing changes between branches. For forks, you can add the fork as a remote and blame using the remote branch: git blame upstream/main file. However, blame only follows the file's history within the current repository. If the file was copied from another repo, blame won't show the original history unless you use --follow for renames. For cross-repo history, you need to use git log --follow or tools like git blame with -C to detect moved code from other files. In practice, if you're investigating a bug that spans branches, blame the file on the branch where the bug was introduced.

terminalBASH
1
2
3
4
5
6
# Blame on a feature branch
git blame feature/login src/auth.js
# Blame on a remote branch
git blame origin/main src/auth.js
# Follow renames
git blame --follow src/auth.js
Output
a1b2c3d4 (Jane Doe 2026-06-01) 1) const login = () => {
🔥Blame is branch-specific
Blame shows the last commit on that branch. If a line was changed in a branch that hasn't been merged, blame on main won't show it.
📊 Production Insight
We once had a hotfix that was merged directly to main but not to the release branch. Blaming the release branch showed the old code, causing confusion. Always blame the branch where the issue occurs.
🎯 Key Takeaway
Specify a branch or remote to blame files in different contexts, and use --follow for renamed files.

Automating Blame in CI/CD Pipelines

You can integrate blame into your CI to automatically flag risky changes. For example, if a line is blamed on a commit that is known to be buggy, you can warn. Or, you can use blame to generate a report of who changed what in a release. A common pattern: after a merge, run git blame on critical files and compare with the previous release. Use git diff --name-only to get changed files, then blame each. This can be done in a script. For example, in a GitHub Action, you can run git blame --since="last release tag" to get all changes since the last tag. Be careful with performance: only blame files that changed. Also, consider using git log instead if you only need commit-level info. Blame is line-level, which is more granular but slower.

ci-blame.shBASH
1
2
3
4
5
6
7
#!/bin/bash
# Get changed files between two tags
CHANGED_FILES=$(git diff --name-only v1.0 v1.1)
for file in $CHANGED_FILES; do
  echo "=== $file ==="
  git blame --since="2026-01-01" "$file" | head -20
done
Output
=== src/app.js ===
a1b2c3d4 (Jane Doe 2026-06-01) 1) const x = 1;
...
⚠ CI blame can be slow
Running blame on many files in CI can increase pipeline time. Limit to critical files or use caching.
📊 Production Insight
We added a CI step that blames any file modified in a PR that touches a security-sensitive module. If the blame shows an author outside the security team, the PR gets flagged for review. This caught several unauthorized changes.
🎯 Key Takeaway
Automate blame in CI to track changes per release, but be mindful of performance.

Common Pitfalls and Misconceptions

One major pitfall: blame shows the last person who modified the line, not necessarily who wrote it. If a line was refactored by a senior dev, blame points to them, even if the original logic was written by a junior. Another: blame doesn't show deletions. If a line was deleted, you can't blame it. Use git log -S or git log -G to find commits that added or removed specific strings. Also, blame on a binary file is meaningless. Git blame only works on text files. Finally, blame can be misleading in merge commits. If a merge conflict was resolved, the resolution is blamed on the merger, not the original author. Always check if the commit is a merge. Use git show --no-commit-header to see the diff.

terminalBASH
1
2
3
# Check if a commit is a merge
git cat-file -p a1b2c3d4 | head -5
# Output shows two parents if merge
Output
tree 4d5e6f7g
parent h8i9j0k1
parent l2m3n4o5
author Jane Doe <jane@example.com> 1718112000 +0000
committer Jane Doe <jane@example.com> 1718112000 +0000
⚠ Blame doesn't show original author after refactors
If a line is moved or reformatted, blame shows the mover, not the writer. Use git log --follow -p on the file to see full history.
📊 Production Insight
A developer was blamed for a bug that was actually introduced by a previous commit that was later refactored. The refactor commit was a merge, and the blame pointed to the merger. We now train the team to check git log on the blamed commit to see if it's a merge.
🎯 Key Takeaway
Blame shows the last modifier, not the original author. Always verify with commit history.

Alternatives to Git Blame: When to Use Git Log Instead

Git blame is line-level; git log is commit-level. If you need to find who introduced a specific string, use git log -S "string" (pickaxe) or git log -G "regex". These search commit diffs, not just the current file. For example, git log -S "TODO" --all finds all commits that added or removed the string "TODO". This is more powerful than blame for finding origins. Another alternative: git shortlog summarizes commits per author. For code review, git diff is better. Blame is best for understanding a specific line's last change. If you need to understand the evolution of a function, use git log -L (line log), which shows the history of a line range. This is like blame but over time.

terminalBASH
1
2
3
4
# Find commits that added or removed 'bug'
git log -S "bug" --oneline
# Show history of lines 10-20
git log -L 10,20:src/app.js --oneline
Output
a1b2c3d4 Fixed bug in login
e5f6g7h8 Added feature
a1b2c3d4 Fixed bug in login
b3c4d5e6 Refactored auth
🔥Use git log -S for string search
When you need to find when a specific piece of code was introduced or removed, git log -S is more effective than blame.
📊 Production Insight
During a security audit, we needed to find all commits that added a vulnerable function. Blame would only show the last change per line. We used git log -S "vulnerableFunc" --all and found 12 commits across branches, including some that were later reverted.
🎯 Key Takeaway
Choose blame for line-level last change, git log for commit-level history or string search.
● Production incidentPOST-MORTEMseverity: high

Blame Pointed at the Wrong Developer in a Production Postmortem

Symptom
A critical production bug was traced to a line of code. git blame showed Alice as the author. Alice had left the company. The postmortem blamed her code. Later investigation proved Bob introduced the bug in a merge conflict resolution two years prior.
Assumption
Everyone assumed git blame shows the original author. The line hadn't been touched since Alice's commit, so she must have written the bug.
Root cause
1. Alice wrote the original code (which was correct). 2. Two years later, Bob resolved a merge conflict in the same area and accidentally kept the wrong version. 3. Bob's merge commit became the 'last modification' according to git blame. 4. When the bug was found, blame pointed at Alice because no one had edited the line since Bob's merge. 5. The merge was invisible to a casual blame read.
Fix
1. Used git log --all --full-history -- <file> to see every commit touching the file, not just the final state. 2. Found Bob's merge commit in the history. 3. Used git show <bobs-merge-sha> to verify the conflict resolution introduced the bug. 4. Re-ran blame with -C -C to detect copy-paste origins. 5. Updated team training: blame shows last touch, not original author. Use git log -S and -C flags for deep tracing.
Key lesson
  • git blame shows the LAST person to touch a line, not the ORIGINAL author.
  • Merge conflict resolutions overwrite blame.
  • Use git blame -w to skip whitespace, -C to detect moved code, and git log -S <string> to trace when a specific piece of code was introduced.
Production debug guideUse this when production is on fire3 entries
Symptom · 01
Need to find who last touched a specific line
Fix
Run git blame -L <start>,<end> <file>
Symptom · 02
Blame shows a formatting commit, want the original author
Fix
Run git blame -w <file> to skip whitespace, or git log --follow -p <file> to trace the real origin
Symptom · 03
Need to find when a specific line was introduced (not last touched)
Fix
Run git log -S 'the-string' -- <file> or git log -L <start>,<end>:<file>
⚙ Quick Reference
8 commands from this guide
FileCommand / CodePurpose
terminalcd /path/to/repoWhat Git Blame Actually Does
terminalgit blame --date=short -L 1,10 README.mdReading Blame Output Like a Pro
terminalgit blame -w src/app.jsIgnoring Whitespace and Moves with Git Blame
terminalgit blame -L 100,200 hugefile.jsBlame in Large Files
terminalgit blame feature/login src/auth.jsBlame Across Branches and Forks
ci-blame.shCHANGED_FILES=$(git diff --name-only v1.0 v1.1)Automating Blame in CI/CD Pipelines
terminalgit cat-file -p a1b2c3d4 | head -5Common Pitfalls and Misconceptions
terminalgit log -S "bug" --onelineAlternatives to Git Blame

Key takeaways

1
git blame shows the LAST person to touch a line, not the ORIGINAL author
2
Use -w to ignore whitespace, -C for copy detection, -S to find introducing commits
3
Maintain .git-blame-ignore-revs to exclude formatting commits
4
Never use blame as sole evidence in postmortems
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
How do I see the full commit message from a blame line?
02
Can I blame a file that was deleted?
03
Why does blame show a commit that only changed whitespace?
04
How can I blame a file from a specific date?
05
What is the difference between git blame and git annotate?
06
How do I blame a file that was renamed?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Everything here is grounded in real deployments.

Follow
Verified
production tested
July 12, 2026
last updated
2,406
articles · all by Naren
🔥

That's Git. Mark it forged?

4 min read · try the examples if you haven't

Previous
Conventional Commits: Standardized Commit Messages
28 / 47 · Git
Next
Git Worktree: Work on Multiple Branches Simultaneously