Gitgit blame

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

Bash
git blame src/app.js

Typical output

Text
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 to git show 1f9ab2c0 to 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:

Bash
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.

Bash
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)
Performance trade-off
Each extra `-C` makes blame compare against more files. On a large repository a triple-`C` blame can take a minute. Use it when you really need to find the origin of a line; otherwise plain `git blame` is fast.
Ignoring whitespace changes

Bash
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

Bash
git blame --ignore-rev 9f8e7d6 src/app.js

Skip a list of commits, project-wide

Bash
# 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

Bash
# 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

Bash
git blame -L 42,42 src/auth/login.js

Text
b3a9f00a (Alice 2026-05-14 09:00:00 +0000 42)   if (user.role === "admin") return true;

2. Read the commit that introduced it

Bash
git show b3a9f00a

3. If that commit was a refactor, dig deeper with -w and -C

Bash
git blame -w -C -L 42,42 src/auth/login.js

4. Watch the function evolve over time

Bash
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 blame opens an annotated split.

Blame is a tool, not an accusation
The word "blame" is unfortunate. In practice it answers "who has context on this line?" — the goal is to find the right person to *ask*, not to point fingers. Use it accordingly. Some teams rename it via `git config alias.who blame`.
Tip
Don't blame raw output — pipe it through `less` or scroll to a specific line: `git blame src/app.js | less +/TODO`. Better, configure your IDE to show inline blame on the current line; you get the answer without leaving the file.