git blame
git blame annotates every line of a file with the commit, date, and author that last touched it. It's the tool you reach for when you read a line of code and ask "why is this here?" The answer is almost always "because of this commit", and the commit message usually tells you the rest.
The basic command
git blame src/app.js
Typical output
1f9ab2c0 (Alice 2026-05-13 10:00:00 +0000 1) function greet(name) {
c204c1d8 (Bob 2025-12-30 09:00:00 +0000 2) if (!name) return "Hello";
c204c1d8 (Bob 2025-12-30 09:00:00 +0000 3) return "Hi " + name;
1f9ab2c0 (Alice 2026-05-13 10:00:00 +0000 4) }Reading a blame line
1f9ab2c0— the commit SHA that last modified this line. Pass it togit show 1f9ab2c0to see the change.(Alice ...)— the author of that commit (configurable via--show-email,--show-name, etc.).2026-05-13 10:00:00 +0000— author timestamp of that commit.1)— the line number in the current version of the file.The text after the parenthesis is the actual line of code.
Limiting which lines to blame
Blaming a 2000-line file is rarely useful. Narrow the range:
git blame -L 30,50 src/app.js # lines 30 through 50 git blame -L 30,+20 src/app.js # 20 lines starting at line 30 git blame -L 30, src/app.js # line 30 to end of file # Blame an entire function by name (uses Git's xfuncname regex) git blame -L :greet:src/app.js # Blame lines matching a regex git blame -L '/TODO/',+5 src/app.js
Detecting moved and copied code
Without help, blame says "Alice moved this code" when Alice merely cut-and-pasted it from another file. The -M and -C flags chase those moves so credit goes to whoever really wrote the line.
git blame -M src/app.js # detect lines moved within this file git blame -C src/app.js # detect lines copied from files modified in the same commit git blame -C -C src/app.js # also from files modified in the COMMIT that created the file git blame -C -C -C src/app.js # also from any file in any commit (slow, very thorough)
Ignoring whitespace changes
git blame -w src/app.js # Ignore changes that are purely whitespace — so a reformatter # commit doesn't take credit for every line it touched.
Ignoring noisy commits
Big reformat commits (prettier rollouts, license header bumps, import sorts) wreck blame. Git lets you skip them.
Skip a single commit
git blame --ignore-rev 9f8e7d6 src/app.js
Skip a list of commits, project-wide
# 1. Create a file (commonly named .git-blame-ignore-revs at repo root) echo "9f8e7d6abc # prettier reformat" >> .git-blame-ignore-revs echo "a1b2c3d4 # license header bump" >> .git-blame-ignore-revs # 2. Tell Git to use it always git config blame.ignoreRevsFile .git-blame-ignore-revs # 3. Now git blame & GitHub both skip those commits automatically git blame src/app.js
GitHub, GitLab, and most modern IDEs read .git-blame-ignore-revs automatically. Commit the file so teammates benefit.
Tracing through renames
Blame doesn't follow renames — log does
# If the file used to be src/old-name.js: git log --follow -p src/new-name.js # Once you find the rename commit, blame the old path at the # commit before that: git blame <sha-before-rename> -- src/old-name.js
Real-world workflow recipe
You're reading code and see a line that looks wrong. Here is the path from question to answer:
1. Blame the suspicious line
git blame -L 42,42 src/auth/login.js
b3a9f00a (Alice 2026-05-14 09:00:00 +0000 42) if (user.role === "admin") return true;
2. Read the commit that introduced it
git show b3a9f00a
3. If that commit was a refactor, dig deeper with -w and -C
git blame -w -C -L 42,42 src/auth/login.js
4. Watch the function evolve over time
git log -L :login:src/auth/login.js
git log -L :function:file is "blame plus history" — it prints every commit that ever touched a specific function, complete with diffs. Often the answer is in commit #2 or #3 back.
GUI blame views
GitHub — open the file, click the Blame button at the top right. Click any commit row to jump to it.
GitLab — same: Blame button on any file view.
VS Code + GitLens — inline blame on the current line, with hover for full commit details, and a Toggle File Blame command for the whole file.
JetBrains IDEs — right-click the gutter → Annotate with Git Blame.
Vim + fugitive —
:Git blameopens an annotated split.