Chapter 7.1 — git blame — Who Wrote This☕ 12 min read

git blame — Who Wrote This

Yeh kaun banaya?! — git blame se detective bano, judge nahi.

01What is git blame and Why It's Not About Accusation

git blame shows the last commit that modified each line of a file, line by line. Every line gets annotated with: commit hash, author, date, and the line content.

The output format is: hash (author date time line_number) content. At a glance, you can see who touched what and when.

The name "blame" is misleading. It is not about pointing fingers or saying "you broke this." It is about finding CONTEXT.

When you see confusing code, git blame tells you WHO to ask and WHEN it was changed. You can then read the commit message to understand WHY.

Alternative names in other tools: git annotate (same output, less aggressive name), git praise (alias some teams use).

Blame is a detective tool, not a weapon. Healthy teams use it to understand decisions, not to assign fault.

GitHub UI has a built-in "Blame" view — navigate to any file and click the "Blame" button.

# Basic blame — see who changed each line
git blame app.js

# Output format:
# a1b2c3d4 (Sai Kumar   2024-01-15 10:30:00  1) function login(user) {
# e5f6g7h8 (Priya Sharma 2024-02-20 14:15:00  2)   if (!user) return null;
# a1b2c3d4 (Sai Kumar   2024-01-15 10:30:00  3)   authenticate(user);
# i9j0k1l2 (Arjun Reddy 2024-03-01 09:00:00  4)   logAccess(user);
# a1b2c3d4 (Sai Kumar   2024-01-15 10:30:00  5) }

# Now you know:
# Line 2 was changed by Priya on Feb 20 — ask her about the null check
# Line 4 was added by Arjun on Mar 1 — ask him about the logging
# Lines 1,3,5 are from the original by Sai
Never use git blame to shame teammates. "You wrote this terrible code!" destroys psychological safety. Use it to learn: "I see you changed this line last — can you help me understand the context?" The commit hash gives you the commit message, which gives you the WHY. Blame gives you the WHO and WHEN to find the WHY.
02Blame Flags — Precision Targeting

git blame -L 10,20 <file> — blame only lines 10 to 20. Essential for large files where full output is overwhelming.

git blame -L :functionName <file> — blame only the function named functionName (regex range). No more scrolling through 500 lines to find your function.

git blame -w — ignore whitespace changes. Someone reformatted? This skips those commits and shows the last MEANINGFUL change.

git blame -M — detect lines moved WITHIN the same file. Shows "Moved from line 15" instead of treating it as new code.

git blame -C — detect lines moved FROM OTHER FILES. Critical for refactoring — shows original author, not the person who copy-pasted.

git blame -C -C — even more aggressive move detection (slower but more accurate for large refactors).

Combine flags: git blame -w -C -M -L :login app.js — the ultimate blame command for understanding a function's true origins.

# Blame only specific lines
git blame -L 10,25 app.js

# Blame a specific function by name
git blame -L :login app.js

# Ignore whitespace (someone ran Prettier — skip those commits)
git blame -w app.js

# Detect moved code WITHIN the file
git blame -M app.js

# Detect code moved FROM OTHER files (copy-paste detection)
git blame -C app.js

# The ULTIMATE blame — find true original author
git blame -w -C -M app.js
# -w: ignore whitespace changes
# -C: detect copy from other files
# -M: detect moves within file
# Result: shows who ACTUALLY wrote the logic, not who moved/formatted it
03Blame vs Log -S — When to Use Which

git blame answers: "Who changed THIS LINE last?" (line-level, current state)

git log -S "text" (pickaxe) answers: "When was this TEXT added or removed?" (commit-level, full history)

Blame only shows the LAST change to each line. If a line was changed 5 times, blame only shows change #5.

git log -S "functionName" <file> shows ALL commits that added/removed "functionName" — full history.

Use blame when: you are reading code and want context for what is currently there.

Use log -S when: a function disappeared and you want to know when/why it was removed.

Use both together: log -S finds when code was introduced, blame shows current state.

# git blame — line-level, current state only
git blame app.js
# Shows who changed each line LAST
# If a line was changed 5 times, only shows #5

# git log -S — commit-level, full history
git log -S "validateUser" app.js
# Shows ALL commits that added/removed "validateUser"
# Full history — when it was added, changed, removed

# Use together for complete understanding:
git log -S "validateUser" --oneline app.js
# abc1234 fix: update validation logic
# def5678 feat: add user validation    ← first introduced here
git blame app.js
# Shows current state of each line
# Combine both: log -S finds the origin, blame shows the present
💡 Pro Tip: Blame is a snapshot of the PRESENT. Log -S is the HISTORY. If you need to know "who deleted this function?", blame will not help (it only shows surviving lines). Use git log -S "functionName" --diff-filter=D to find when it was deleted and by whom. We will cover pickaxe search in depth in Chapter 7.5.
04Reading Blame Output Like a Detective

Blame output has a specific structure: hash (author date time line_number) content

The hash is your KEY — use git show <hash> to see the full commit: message, all changed files, and the WHY.

If hash shows ^ before it (like ^a1b2c3d4), that line is from the initial commit of the file.

If you see the same hash on many consecutive lines, that commit changed a large block — read that commit message for the full story.

If you see many different hashes in a small area, that code is a "hot spot" — changed frequently, likely buggy or contested.

Pro tip: git blame → find interesting hash → git show <hash> → understand the full context → now you know WHY.

# Step 1: Blame the file
git blame app.js
# a1b2c3d4 (Sai  2024-01-15  1) function login(user) {
# e5f6g7h8 (Priya 2024-02-20  2)   if (!user) return null;

# Step 2: Interesting line? Investigate the commit
git show e5f6g7h8
# commit e5f6g7h8
# Author: Priya Sharma
# Date:   Tue Feb 20 14:15:00 2024
#
#     fix: handle null user in login flow
#
#     JIRA-456: Users clicking deep links without session
#     caused crash on login screen. Added null guard.

# Step 3: NOW you understand — Priya did not write bad code.
# She FIXED a crash bug. The null check is intentional.

# Find hot spots — lines changed frequently
git blame app.js | awk '{print $1}' | sort | uniq -c | sort -rn
#   15 a1b2c3d4  ← 15 lines from this commit (stable)
#    8 e5f6g7h8  ← 8 lines from this commit
#    3 m3n4o5p6  ← 3 lines, recent change
05Blame in Practice — Real-World Workflows

During code review: see a suspicious line → blame → understand context before commenting. You might discover it was a deliberate fix, not a mistake.

During debugging: found the buggy line → blame → find the commit that introduced it → read the message → understand the root cause.

During refactoring: want to delete code → blame → check if it is still needed or if it was a temporary fix that can now be removed.

GitHub's blame view: navigate to file → click "Blame" button. Click any commit hash to see the full diff.

VS Code extension: "GitLens" adds inline blame annotations — see author and date next to every line as you code.

JetBrains IDEs (WebStorm, IntelliJ): built-in "Annotate" feature (right-click gutter → Git → Annotate).

Remember: the person blamed might just be the last one who formatted the code, not the original author. Use -w flag.

# Real workflow: Debugging a production error
# 1. Find the error line
grep -n "Cannot read property" app.js
# 42:    return user.name.toUpperCase();

# 2. Blame that area
git blame -L 40,45 app.js
# abc1234 (Sai 2024-01-10 40) function getDisplayName(user) {
# def5678 (Ravi 2024-03-05 41)   if (!user) return "Guest";
# abc1234 (Sai 2024-01-10 42)     return user.name.toUpperCase();
# abc1234 (Sai 2024-01-10 43) }

# 3. Line 42 is from Jan 10 by Sai. Why no null check on name?
git show abc1234
# commit abc1234
# Author: Sai Kumar
#     feat: add user display name helper

# 4. Ravi added null check for user on Mar 5, but not for name!
# The bug: user exists but user.name is null.
# Fix: add optional chaining user.name?.toUpperCase()

# 5. Now you can fix it with full understanding of the history

Lo kar liya — Key Points:

  • ✅ git blame shows the last commit that modified each line — who, when, and the commit hash
  • ✅ Blame is for understanding context, not assigning fault — use it to find WHO to ask about WHY code exists
  • ✅ Use -L to blame specific lines or functions: git blame -L 10,20 file or git blame -L :funcName file
  • ✅ Use -w to ignore whitespace-only changes, -C to detect code copied from other files, -M for moved code
  • ✅ Blame only shows the LAST change per line — for full history of additions/removals, use git log -S (pickaxe)
  • ✅ The commit hash in blame output is your key — git show that hash to understand the WHY behind the change
  • ✅ GitLens (VS Code) and IDE "Annotate" features provide inline blame without running terminal commands
Course Search
Search across all chapters & stages
📖

Search the course

Type any topic — branching, stash, rebase, hooks — and jump straight to that chapter.

merge branchesgit stashundo commitrebase