Run git blame on a file and every line comes back with the commit, author and date of its last change printed beside it.
People use it to trace a bug to the commit behind it, or to find someone who can explain a confusing block. Git’s reference files it under Debugging, right next to git bisect.
The name on each line belongs to the last editor, not the original author. A reformat commit therefore takes credit for every line it touches, and that’s the main thing to watch for. Ignored-revision lists and move detection deal with it.
What Is git blame?

Think of it as a read-only report. It works on one file at a time and answers a narrow question for each line: which commit touched this last, and who made it.
Standard version control history stops at the commit. git blame goes one level lower, down to the individual line.
For every line, the report gives the most recent commit that changed it, plus that commit’s author and date. Lines that were deleted or replaced don’t appear at all. The Git manual hands those over to git diff and the pickaxe search (git log -S).
Any machine with Git already has the command, since it ships with the suite. The name sounds harsh, but the output is neutral. A hash, a name, a date.
How Do You Read git blame Output?

Each row of default output puts the abbreviated commit hash, the author name, the author date and the line number in front of the line itself.
Pro Git shows a typical row from the Linux kernel Makefile:
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 71) endifRead it left to right. The hash points to the commit, the name and date come from that commit (the date includes a timezone offset), and 71 is where the line sits in the current file.
Paste the commit hash into git show to read the full change and its message.
The leading ^ marks a boundary commit. In the Pro Git example, those lines came from the repository’s initial commit and haven’t changed since. The -b option blanks the hash for boundary commits, and –root stops root commits from counting as boundaries.
A handful of flags reshape the row. Use -e to see the author email instead of the name, -t for the raw timestamp, -l for the full-length hash, and -s to drop the author and timestamp entirely. If a line moved, -n adds its original line number and -f adds the original file name.
Settings such as blame.showEmail and blame.date make those choices permanent through git config.
The default abbreviation is 7 hexadecimal digits according to the Git 2.56.0 manual, and –abbrev=n sets a longer minimum.
Machine-Readable Formats
Editors and review tools don’t parse the default row. They read one of the dedicated formats below.
| Option | What it prints | Use it for |
|---|---|---|
| –porcelain | A header per line group, commit details once per commit | Scripts reading large files |
| –line-porcelain | Full commit details repeated on every line | Simple parsers, per-line counting |
| –incremental | Results as they are built, out of order, no file content | Interactive viewers |
The porcelain header carries the 40-byte SHA-1, the original and final line numbers, and the group size. Author, author-mail, author-time, author-tz and the commit summary follow, then the line content prefixed by a tab.
The manual’s own example counts lines per author by pulling the author field out of –line-porcelain output. That works because every line repeats its commit details.
How to Run git blame on a File, a Line Range or a Function

Give git blame a file path to annotate the whole thing, then add -L to shrink the output to a line range or a single function.
The full form in the Git 2.56.0 manual is git blame [options] [revision] [–] file. The double hyphen separates the path from revision arguments.
- Annotate the whole file with git blame path/to/file.
- Limit the range with -L 40,60, or with -L 40,+21 for 21 lines starting at line 40. The manual states both forms ask for the same lines.
- Blame one function with -L :funcname. Git takes the range from the first line matching the name to the next function line, using the same rules git diff uses for hunk headers.
- Narrow the history with a revision such as v2.6.18.. or with –since=3.weeks.
- Take the hash from any row and open it with git show.
-L accepts regular expressions at both ends and can be repeated, with overlapping ranges allowed. A +N or -N offset is only valid as the end value.
One limit to keep in mind. A revision range changes what a row means, because a line blamed on the boundary commit is unchanged since that point, not necessarily introduced there.
How Do You Track Moved and Copied Lines with git blame?
Add -M to follow lines moved within a file, -C to follow lines moved or copied from other files, and -w to ignore whitespace-only edits.
Without them, git blame credits whichever commit moved the code. Say a round of code refactoring swaps two blocks. The traditional algorithm notices only half of that movement, per the Git manual.
Lines moved up get blamed on the parent, while lines moved down get blamed on the child commit. -M runs extra passes so both groups land on the parent.
| Flag | What it detects | Search scope | Cost |
|---|---|---|---|
| -w | Whitespace-only differences | Same file | Does not follow moves |
| -M | Lines moved or copied within a file | Same file | Extra inspection passes |
| -C | Lines moved or copied from other files | Files changed in the same commit | Searches other files |
| -C -C | Same, plus copies at file creation | The commit that creates the file | Wider search |
| -C -C -C | Copies from any earlier commit | Any commit | Widest search |
-M and -C take an optional number, the minimum count of alphanumeric characters Git must see moved before it ties lines to the parent commit. The defaults are 20 for -M and 40 for -C (Git manual), and –score-debug prints the score behind each decision.
Pro Git shows what this does on a file split. After GITServerHandler.m was divided into several files, blaming GITPackUpload.m with -C credited its lines to the original file and commits, not to the commit that copied them over.
Whole-file renames need no flag at all. Git follows them automatically, and the manual lists no switch to turn that off.
The manual also describes a review trick. Blame a newly added file with -C -C against the commit that created it, and the output shows which lines were pasted from elsewhere.
My own habit is to start with -w and -M, then add -C once code has crossed files. Save -C -C -C for a bounded -L range, since every extra -C widens the search and the run time grows with it.
How to Skip Formatting Commits with –ignore-rev and .git-blame-ignore-revs
Pass –ignore-rev for one commit or –ignore-revs-file for a list, and Git blames the affected lines on the previous commit as if the formatting change never happened.
The Git manual says lines changed or added by an ignored commit go to the previous commit that touched that line or a nearby one. Both options can be repeated.
- Find the full, unabbreviated hash of each formatting or bulk-refactor commit.
- Write one hash per line in a file named .git-blame-ignore-revs at the repository root. Lines starting with # are comments.
- Test it with git blame –ignore-revs-file .git-blame-ignore-revs path/to/file.
- Make it the default with git config blame.ignoreRevsFile .git-blame-ignore-revs.
- Commit and push the file so teammates and hosted blame views read it.
The Git manual doesn’t name the file. GitHub’s blame view only reads a file called .git-blame-ignore-revs from the repository root, so the conventional name is the one that works everywhere.
The X.Org libXScrnSaver project keeps one, with entries for a whitespace fix and a trailing-whitespace strip.
Two config options flag the affected lines. blame.markIgnoredLines prints ? on lines handed to another commit, and blame.markUnblamableLines prints \* on lines with no earlier commit to blame.
Support differs by platform (as of October 2026). On the Git CLI, both options arrived in Git 2.23, according to the Black documentation. GitHub reads the root file automatically and shows an “Ignoring revisions” banner.
GitLab is the odd one out. It introduced the feature in 17.10 behind the blame\_ignore\_revs flag, and it became generally available in 17.11.
Sources disagree on GitLab’s default. Black’s documentation says GitLab supports the file in blame views by default, while GitLab’s own documentation has users tick Ignore specific revisions under Blame preferences.
GitHub’s documentation adds a caveat of its own: a commit that was the last to modify a line still appears in blame. The Git manual words the rule more broadly, so test one real file before trusting either.
On GitHub, appending ~ to the SHA in the URL drops the banner and shows the unfiltered view.
There’s also a way this can bite you. A comment in the libXScrnSaver file warns that with blame.ignoreRevsFile set, switching to a branch without the file makes git blame fail with an error.
Where Can You Use Blame in GitHub, GitLab, VS Code and JetBrains?
Each platform wraps git blame in its own view. GitHub and GitLab have a Blame button, Visual Studio Code gets inline line annotations from GitLens, and JetBrains IDEs offer Annotate with Git Blame.
On GitHub, click Blame above the file content to get a line-by-line history with the code grouped by commit. Each group lists the author, commit description and date, and the versions icon opens the file as it stood before that commit.
GitLab opens blame from the file header or a full blame page, and only for text files. View blame prior to this change steps back through earlier revisions of a line. Inline blame in the file view has been enabled by default since GitLab 19.1, and GitLab’s documentation lists general availability for 19.5, which is scheduled for release on October 15, 2026. Binary files such as images and PDFs are not supported, according to GitLab’s documentation.
In Visual Studio Code, the GitLens extension puts current line blame at the end of the active line, showing author, date and commit message by default. It also adds a status bar blame, file blame in the gutter, and a Blame Previous Revision action in the line hover.
In JetBrains IDEs, right-click the gutter and choose Annotate with Git Blame. The annotate options map straight to flags. Ignore Whitespaces is -w, Detect Movements Within File is -M, and Detect Movements Across Files is -C.
GitHub’s and GitLab’s blame documentation list no controls for -M or -C. For move tracking, the command line or a JetBrains IDE gives you more say over the result.
Atlassian’s Git tutorial also lists Bitbucket among the hosts with a blame view.
git blame vs git log, git log -L, git log -S and git bisect

Use git blame when you already know the line. Use git log -L when you need that line’s full history, git log -S when you need the commit that added or removed a string, and git bisect when you have no idea where the bug lives.
| Command | Scope | Question it answers | Pick it when |
|---|---|---|---|
| git blame | One file, per line | Who changed this line last? | You know the line |
| git log -L | Line range or function | How did this code evolve? | The blamed commit is not the origin |
| git log -S | Whole history | Which commit added or removed this string? | The line was deleted or replaced |
| git bisect | Whole project | Which commit broke it? | You have a good and a bad commit, no line |
The -L option of git log takes the same range forms as blame, but the file goes at the end: -L 40,60:path or -L :funcname:path.
It prints each commit that touched the range, patch included. The manual limits it to one starting revision and no pathspec limiters.
git log -S searches the diff of each commit for a text string. The blame manual points to it for tracking when a snippet was added, moved, deleted or replaced.
There’s one catch. Merge commits show no diff and don’t match -S unless a –diff-merges variant is set (git log manual), so a change that entered through a merge can slip past the search.
git bisect runs a binary search between a known good and a known bad commit. The manual’s own example opens with 675 revisions left to test, roughly 10 steps.
With git bisect run, a script decides each step. Exit code 0 marks a commit good, 1 to 127 (except 125) marks it bad, and 125 skips a commit that can’t be tested.
In practice the order goes like this. Start with git blame once you hold a file and a line. If blame lands on a formatting or merge commit, move to git log -L. When the line no longer exists, switch to git log -S. And if all you know is the symptom, reach for git bisect.
When git blame Shows the Wrong Author or Nothing at All

git blame reports only the last change to each line, so any later edit, reformat, move or squash hides the original author.
Reformats and bulk refactors
A formatting commit takes over every line it touches. The ignore-revs file and -w fix that, but only if someone recorded the commit’s hash.
Squash merges
Squashing a pull request turns all of its commits into one commit on the base branch, according to GitHub’s documentation, so blame lands on that single commit.
GitHub’s default squash message is the commit’s own title and message when the pull request has one commit, and the pull request title plus the list of commits when it has two or more (repository admins can pick other formats). The original titles stay readable even though the individual commits are gone.
Merge-commit workflows behave differently. –first-parent reports when a line reached a particular integration branch, per the Git manual.
Deleted or replaced lines
blame annotates only lines that exist in the file. The manual sends you to git diff or the pickaxe search for everything else.
–reverse walks history forward and shows the last revision in which a line still existed. It needs a range such as start..end, with the file present at the start.
Slow runs on large histories
In a GitLab issue (January 2026), a staging request for the first 70 lines of the Linux kernel MAINTAINERS file took about 8 seconds, mostly in Gitaly, and running git blame -L 1,70 MAINTAINERS locally took about the same. A later benchmark in a related GitLab merge request measured about 3 seconds locally, so timings vary by hardware.
A line range limits the output, not the history walk. Files with long histories stay slow with or without -L.
Common Questions About git blame
Why is it called git blame?
Fredrik Kuivinen added git-blame in February 2006 with a commit titled “Add git-blame, a tool for assigning blame” (git mailing list, 2006).
He later told the author of a DEV Community post that he chose the name because other tools already called the feature blame.
What is the difference between git blame and git annotate?
Only the default output format. Junio Hamano wrote in 2008 that both run the same engine, and git annotate survives as a backward-compatibility alias.
Adding -c makes git blame print annotate-style output.
Does git blame assign fault?
No. The output names the last author and commit of each line and says nothing about whose mistake it was.
The useful move is to read that commit’s message, then ask the author what the change was meant to do.
How does git blame treat uncommitted changes?
It blames lines modified locally in the working tree on the working tree itself.
A Git source commit message by Junio Hamano states the rule: find each line’s origin in the history leading to HEAD, then blame the locally modified lines.
Can I give git blame a friendlier name?
Yes, with a Git alias. Running git config –global alias.praise blame makes git praise run the same command.
An alias renames only the command. The flags and the output stay identical.
Keeping git blame Useful After a Large Reformat

A team keeps git blame readable by recording a reformat commit’s hash only after the commit lands on the base branch. Squash and rebase merges create new commits, so the hash on the pull request branch is the wrong one.
The merge strategy in a team’s git workflow sets the order. Merge the formatter run as its own pull request, copy the hash from the base branch, then add it in a follow-up commit and set blame.ignoreRevsFile.
Keep the formatter run free of logic changes. Edits mixed into it receive the wrong attribution, because every line the ignored commit touched goes to an earlier commit.
The cost is drift. A line can land on a commit that edited a nearby line, so the hash still deserves a look at its diff.
This position was verified in October 2026. GitLab applies the ignore list per user through a Blame preferences checkbox, and a default setting would remove that step.
- MongoDB Cheat Sheet - October 20, 2026
- Adding AI Video Generation to a Software Product: Architecture, Cost Guardrails and QA - October 20, 2026
- Show Hidden Characters, Spaces, and Line Endings in Notepad++ - October 19, 2026




