Hiding formatting commits from the blame history
A single large formatting commit is enough to make git blame useless for an entire file, since every line suddenly points to that one commit instead of the actual functional change. The .git-blame-ignore-revs file solves exactly this problem without touching the commit history itself.
Table of Contents
- 1. How formatting commits ruin the blame history
- 2. Basics: the .git-blame-ignore-revs file
- 3. Permanent configuration with blame.ignoreRevsFile
- 4. Support in PhpStorm and VS Code
- 5. Support in GitHub and GitLab
- 6. Limitations: git log, bisect, and rebase
- 7. Workflow: registering a new formatting commit
- 8. Automation: a CI check for staleness
- 9. Alternative strategies to avoid the problem
- 10. Summary
- 11. FAQ
1. How formatting commits ruin the blame history
As soon as a team introduces an automatic formatter such as Prettier, PHP-CS-Fixer, or Black, the usual result is one large commit that reformats the entire existing codebase in one go. That makes sense from a code quality standpoint, but it has an unpleasant side effect: git blame subsequently shows that exact formatting commit for practically every line in the project, regardless of who actually last changed the line functionally and why.
That is exactly where git blame loses its usefulness, right at the question of who introduced a given line and for what reason. Anyone inspecting a line after the reformat keeps landing on the same formatting commit and has to work through the real history laboriously with git log -L or repeated blame calls against the parent commit.
2. Basics: the .git-blame-ignore-revs file
Since Git 2.23, git blame supports the --ignore-revs-file option, which accepts a list of commit hashes to skip during blame calculation. When a line's blame lands on an ignored commit, Git automatically keeps searching in the parent commit until a line lands on a non-ignored commit that actually represents the functional change.
The convention that has emerged is to maintain this list in a file named .git-blame-ignore-revs at the root of the repository, one commit hash per line with optional comments starting with a hash sign. This file is versioned like any other and grows with every further large, purely mechanical commit.
# .git-blame-ignore-revs
# Introduced Prettier across the whole project
a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2
# Updated PHP-CS-Fixer rule set to PSR-12
9f8e7d6c5b4a9f8e7d6c5b4a9f8e7d6c5b4a9f8e
# One-off manual application with this file
git blame --ignore-revs-file=.git-blame-ignore-revs path/to/file.php
3. Permanent configuration with blame.ignoreRevsFile
Passing the path manually on every git blame call is impractical, so Git lets you register the file permanently via git config blame.ignoreRevsFile. After that, every git blame call in the repository automatically honors the list without needing extra flags.
This configuration is local and has to be set once by every developer, which is why a note in the README or an automated setup script is a good idea, similar to other local git config settings. Alternatively, a team can distribute the setting project wide through a shared .gitconfig using include.path.
git config blame.ignoreRevsFile .git-blame-ignore-revs
# After that, the normal call is enough
git blame path/to/file.php
4. Support in PhpStorm and VS Code
Both PhpStorm and Visual Studio Code respect the blame.ignoreRevsFile configuration as long as it is set locally via git config, because both editors ultimately call the system wide Git binary with the local configuration. That means the annotate or blame view in the editor automatically skips the ignored commits once the configuration is set, with no editor specific setting needed.
In practice it is still worth checking after an editor update or a fresh install whether the blame view actually skips the expected commits, since some editor versions only pick up the configuration after a restart or an explicit cache invalidation.
5. Support in GitHub and GitLab
GitHub automatically honors a file named .git-blame-ignore-revs at the repository root in the blame view of its web interface, with no additional configuration required, as long as the file has exactly that name. GitLab offers comparable support through the same convention, so a team that maintains the file correctly once benefits from consistent behavior across editor, terminal, and web interface.
It is worth noting that both platforms only recognize the file name at the repository root and do not honor any alternative configuration via git config, since they obviously cannot rely on the local configuration of one individual developer. The file is therefore the only portable place to share this information across platforms.
6. Limitations: git log, bisect, and rebase
The ignore list only affects git blame and the annotate views derived from it, not git log, git bisect, or other commands that traverse the commit history. A formatting commit therefore still shows up normally in git log and can be tested as a regular commit during a git bisect session, which occasionally produces false positives if the formatting commit accidentally changed behavior as well.
During an interactive rebase that rewrites commits before the formatting commit, its hash and the hashes of every following commit inevitably change too, which invalidates the entries in .git-blame-ignore-revs. For branches that get rebased regularly, it is therefore worth maintaining the ignore list only after the final merge into the main branch, where hashes stay stable.
7. Workflow: registering a new formatting commit
A proven process is to create the formatting commit in isolation, without any functional changes in the same commit, so it can be cleanly ignored later without accidentally hiding real changes as well. Right after the commit, its hash is retrieved with git log -1 --format=%H and added to the file with a short comment.
It also makes sense to add this step to the pull request checklist for large, purely mechanical changes, so it does not depend on individual memory. Some teams even automate the entry with a small script that appends the last commit hash right after a formatter run.
#!/usr/bin/env bash
# scripts/track-formatting-commit.sh
set -euo pipefail
HASH="$(git log -1 --format=%H)"
MESSAGE="$(git log -1 --format=%s)"
{
echo ""
echo "# ${MESSAGE}"
echo "${HASH}"
} >> .git-blame-ignore-revs
echo "Added commit ${HASH} to .git-blame-ignore-revs."
8. Automation: a CI check for staleness
To keep the ignore list from silently going stale, an automated reminder in the pull request process is worth setting up: a CI job can check whether a commit contains only whitespace or formatting changes, for example via git diff --ignore-all-space compared against a regular diff, and emit a warning in that case if the commit hash is not yet present in .git-blame-ignore-revs.
Such a check does not replace a human decision, since not every large diff is automatically a pure formatting commit, but it reliably prevents a team from simply forgetting to add the entry, which in practice is the most common reason for a stale ignore list.
9. Alternative strategies to avoid the problem
Instead of fixing the problem after the fact, it can partly be avoided from the start by splitting formatting changes into smaller, topically scoped commits, for example per directory or module instead of the whole repository at once. That does not reduce the number of affected lines, but it spreads the ignore entries more finely and makes each individual commit easier to reason about.
A complementary strategy is enforcing formatting rules from the start through a pre-commit hook, so large after-the-fact formatting commits stop being necessary altogether, since every commit is already correctly formatted at creation time. For existing repositories, though, .git-blame-ignore-revs remains the most pragmatic path, because it leaves the past untouched and takes effect immediately.
| Tool | Honors .git-blame-ignore-revs | Configuration path | Affects git log |
|---|---|---|---|
| git blame (terminal) | Yes | blame.ignoreRevsFile | No |
| PhpStorm Annotate | Yes | Local git config | No |
| VS Code GitLens/Blame | Yes | Local git config | No |
| GitHub web blame | Yes | File name at root | No |
| GitLab web blame | Yes | File name at root | No |
Mironsoft
Git workflows, branching strategies, and CI hooks
Chaotic Git history and unclear branching rules across the team?
We set up clean Git workflows, clarify branching strategies for the team, and automate quality checks via Git hooks and CI pipelines so the history stays traceable.
Workflow Audit
Review the existing branching strategy and merge practice for weak spots.
Hook Automation
Set up pre-commit and pre-push hooks for linting, tests, and commit conventions.
Team Training
Teach rebase, cherry-pick, and conflict resolution hands-on across the team.
10. Summary
.git-blame-ignore-revs
Mechanism
git blame --ignore-revs-file skips to the parent commit
Configuration
git config blame.ignoreRevsFile .git-blame-ignore-revs
Platforms
GitHub and GitLab auto-detect the file at the repo root
Limitation
Does not affect git log, bisect, or the history itself