skip to content

What does git blame report for each line, and what can it not tell you?

level: juniorimportance: must knowfreq 60%

answer

  1. One commit per line, not many
  2. Last touched, not authored
  3. Narrow to a range first
  4. Re-blame from before that commit
  5. Deleted lines need a different mode

basics

~20 s

git blame annotates every line of a file with the commit that last modified it, plus that commit's author and date. It shows only the most recent touch, never the line's full history, and it says nothing about deleted lines.

solid answer

~50 s

`git blame <file>` prints the file with each line prefixed by the commit that **last changed** that line, its author and timestamp, and the line's number in that original commit. `-L 40,80 <file>` restricts it to a line range, and `-L :funcName:<file>` restricts it to a function. The key limitation is that blame answers "who touched this last", not "where did this line come from": a whitespace fix, a rename or a mass reformat becomes the answer for every line it touched. To go further back you re-blame from before that commit — `git blame <commit>^ -- <file>` — repeating until you reach a meaningful change. Blame also cannot show you a line that no longer exists; that requires `git blame --reverse` or a log-based search. Treat it as an archaeology tool for understanding code, not an accountability tool for assigning fault.

go deeper

for a junior

Know that blame annotates each line with the commit that last changed it, that -L narrows to a range, and that the result is a starting point you follow up with git show.

for a middle

Explain the last-touch limitation and demonstrate the drill-down loop with git blame <commit>^ -- <file>, plus -w for whitespace noise.

for a senior

Show that you know what blame cannot do — deleted lines, full line history — and reach for --reverse or a log-based approach instead of forcing blame to answer the wrong question.

for a principal

Connect it to practice: archaeology is only as good as the commit messages and the commit granularity a team enforces, so investment in message quality pays off years later during incidents.

## What blame computes `git blame <path>` walks history backwards from a starting revision (default `HEAD`) and, for every line currently in the file, determines the last commit that introduced or modified that line. The output prefixes each line with: - the abbreviated commit id, - the author's name, - the author timestamp, - the line number the line had in that commit, followed by the line's content. Useful presentation flags: `-s` suppresses the author and date for a compact view, `-e` shows email addresses instead of names, `-l` prints the full commit id, and `--date=short` changes timestamp formatting. `--porcelain` and `--line-porcelain` produce stable machine-readable output for tooling. ## Narrowing the question Blaming a two-thousand-line file is rarely useful. `-L` restricts the region: - `git blame -L 100,140 src/app.py` — lines 100 through 140. - `git blame -L 100,+20 src/app.py` — twenty lines starting at 100. - `git blame -L :parse_config:src/app.py` — the region Git recognizes as that function, using the language-aware function-name detection configured for the file type. You can pass `-L` more than once to blame several regions at a time. ## Starting somewhere other than HEAD `git blame <rev> -- <path>` blames the file as it existed at that revision. The important variant is `git blame <commit>^ -- <path>`: blame the file as of the commit *before* a given one. This is the manual drill-down loop. Blame a line, find that it points at commit X which is only a rename or a reformat, then re-run blame from `X^` to see who touched the line before X. Repeat until you reach the commit that actually created the logic. Experienced users do this by reflex; it is the single most useful blame technique after `-L`. ## What blame cannot answer 1. **The line's full history.** Blame reports one commit per line — the most recent. A line rewritten five times shows only the fifth. 2. **Deleted lines.** If the code you are looking for no longer exists, blame on the current file will never find it: blame starts from the lines that are there. `git blame --reverse <start>..<end> -- <path>` inverts the walk and reports, for each line, the commit in which it last existed — the way to find when something was removed. 3. **Why.** Blame gives you a commit id; the reasoning lives in the commit message, and in whatever discussion the message references. A blame result whose commit message is "fix" teaches you nothing, which is the practical argument for writing real commit messages. 4. **Intent versus mechanics.** A commit that reindented a whole file is the honest answer to "who last modified this line" and a useless answer to "who wrote this logic". Git provides explicit machinery to look past such commits. ## Blame is not blame The interviewer's framing matters here and candidates are frequently scored on it. Reaching for blame to identify who to hold responsible is a cultural red flag; reaching for it to understand *why* unfamiliar code looks the way it does — then reading the commit message and the surrounding change — is the professional use. The Git project itself offers `git annotate` as a synonym precisely because the name is loaded. ## Whitespace noise `git blame -w` ignores whitespace when deciding whether a line changed, so a reindentation stops shadowing the real author. Combined with the move- and copy-detection options and with an ignore-revs file for known formatting commits, `-w` is the first line of defence against archaeology that dead-ends in a formatting commit. ## Interactive alternatives Blame output is a starting point rather than a destination: once you have a commit id, `git show <commit>` gives you the whole change and its message, which is usually where the actual explanation lives. Many people run blame, note two or three commit ids, and spend most of their time in `git show` afterwards.

  • A blamed line points at a commit that only reindented the file. How do you find the real change?
    Re-run blame from before that commit: git blame <commit>^ -- <file>, optionally with -L for the same range. Repeat until you reach a commit that changed logic. Adding -w so whitespace-only edits stop counting as changes usually skips such commits in one step.
  • How do you find out when a line that no longer exists was deleted?
    Ordinary blame cannot, because it starts from lines that are present. git blame --reverse <old>..<new> -- <file> walks forward instead and reports, for each line, the last commit in which it still existed, which pinpoints the removal.
  • How do you blame only one function rather than the whole file?
    Use the function form of -L: git blame -L :functionName:path/to/file. Git uses its language-aware function-name detection to find the region, so you get just that function's lines. The numeric forms -L 100,140 and -L 100,+20 work the same way for arbitrary ranges.
  • Why do interviewers care how you frame the use of git blame?
    Because using it to assign fault is a culture problem, while using it to understand unfamiliar code is engineering. The expected framing is: find the commit, read its message and full diff with git show, and use that context to decide whether the code can safely change.

saying these in an interview costs you the question

  • Thinks blame shows a line's full history
  • Uses blame to assign fault for bugs
  • Expects blame to find deleted lines
  • Blames an entire large file at once
  • Stops at a reformatting commit and gives up

context