Great work!

XP to next level

BugEater

Blame Across Renames and Reformats

Learning Objectives

By the end of this lesson you will be able to:

  • Recognise when blame is reporting a cosmetic change rather than a real one
  • Follow a line through a file rename or a code move
  • Configure your repository to ignore bulk-reformatting commits permanently
  • Know when to stop trusting blame and switch tools

When Blame Lies to You

Blame reports the last commit that touched each line. That is precise, and it is frequently useless, because a great many commits touch lines without changing anything that matters:

  • A Prettier or Black run reformats 4,000 lines
  • Tabs become spaces across the whole repository
  • A file moves from src/views/ to src/components/views/
  • A function is cut from one file and pasted into another
  • Someone adds a licence header to every source file

After any of these, blame reports one commit and one author for enormous swathes of code. Everything genuinely interesting is hidden behind it.

Seeing Past Whitespace

git blame -w src/views/LoginView.js

Ignores changes that are whitespace-only. This handles indentation changes, trailing-space cleanups and tabs-to-spaces conversions.

It does not handle a reformat that also rewraps lines or reorders arguments — those are real content changes as far as Git is concerned.

Following Code That Moved

Two flags, increasingly aggressive:

git blame -C src/components/views/LoginView.js

-C detects lines copied or moved from other files in the same commit. If a function was cut from AuthForm.js and pasted here, -C follows it back and reports its original author instead of the person who did the move.

git blame -C -C -C src/components/views/LoginView.js

Repeating -C widens the search: two look at files modified in the same commit, three search the entire repository at each commit. It is markedly slower — on a large repository this can take a minute — but for "this code definitely came from somewhere, where?" it is the tool.

For file renames, Git handles them automatically. A pure git mv is detected and blame follows through it with no flags at all. You only need -C when the content moved between files rather than the file itself moving.

The Permanent Fix: .git-blame-ignore-revs

If your project has had a big reformatting commit, you should not have to remember -w forever. Git supports an ignore list.

1. Create a file at the repository root listing the full commit IDs to ignore:

# .git-blame-ignore-revs
# Bulk reformats — not meaningful authorship.

# Prettier across the whole codebase (2026-05-11)
d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3

# Tabs to spaces (2026-02-03)
a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0

Use full 40-character IDs — abbreviated ones are rejected.

2. Tell Git to use it:

git config blame.ignoreRevsFile .git-blame-ignore-revs

3. Commit the file so everyone benefits.

From then on, plain git blame skips past those commits to the real previous author. GitHub, GitLab and most IDEs read this file too, so the web blame view improves as well.

This is one of the highest-value fifteen-minute contributions a tester can make to a codebase they investigate often.

Following a Single Line Through Time

When you need the whole story of one line rather than its last change:

git log -L 41,41:src/views/LoginView.js

This prints every commit that ever changed line 41, newest first, each with its diff. It follows the line as it moves up and down the file, and through renames.

git log -L '/LOGIN_BUTTON_ID/',+1:src/views/LoginView.js

The same, anchored to a symbol rather than a line number — much more robust, since line numbers change constantly.

This is the command that turns "who last touched this?" into "what has this line's entire life been?" and it is worth remembering when blame's single answer isn't enough.

When to Stop Using Blame

Blame answers "who changed this line?". Some questions look similar but need different tools:

Question Tool
Who last changed this line? git blame
What is this line's full history? git log -L
When did this string first appear anywhere? git log -S (next lesson)
Which commit made this test start failing? git bisect

git bisect deserves a mention even though it is beyond this course: it binary-searches history by running a command at each step, and it will find the exact breaking commit in about ten steps across a thousand commits. When blame can't help because the failure isn't tied to one visible line, that is where to go next.

Pro Tip: Before investigating any unfamiliar repository, check whether .git-blame-ignore-revs exists. If it doesn't and the project has had a big reformat, creating one is a small, permanent improvement to everyone's ability to investigate.

Key Takeaways

  • Reformats, moves and mass edits make blame report a cosmetic commit instead of a real one
  • -w ignores whitespace-only changes; it does not survive rewrapping or reordering
  • -C follows lines moved or copied from other files; repeat it to widen the search
  • File renames are followed automatically — -C is for content moving between files
  • .git-blame-ignore-revs plus blame.ignoreRevsFile fixes it permanently, for everyone
  • git log -L gives one line's complete history; git bisect finds a breaking commit blame can't

Quiz

Every line of a file blames the same commit from last Tuesday. What most likely happened?

A function was cut from AuthForm.js and pasted into LoginView.js. Which flag makes blame report its original author?

What does .git-blame-ignore-revs do once blame.ignoreRevsFile is configured?

You want the complete history of one specific line, not just its last change. Which command?