.git-blame-ignore-revs: Hiding Formatting Commits from git blame
AI generated
git
HEAD
Git · Code History
.git-blame-ignore-revs
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.

8 min read Git git blame Code History

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

11. FAQ: .git-blame-ignore-revs

1Starting with which Git version does --ignore-revs-file work?
The option was introduced in Git 2.23, available since late 2019. Practically every Git installation in current use already supports it, so a separate version check is rarely necessary in modern teams.
2Does every developer need to configure blame.ignoreRevsFile themselves?
Yes, because it is a local git config setting that is not picked up automatically from the repository. A setup script or a note in the README ensures nobody forgets this step.
3What happens if a commit hash in the file does not exist, for example after a rebase?
git blame silently ignores invalid or unresolvable hashes without throwing an error. That does mean, though, that affected lines can fall back to the original, now stale formatting commit after a rebase.
4Can I use multiple ignore files at the same time?
Yes, with multiple --ignore-revs-file flags or multiple blame.ignoreRevsFile configuration lines you can combine several files, for example a shared team file plus a personal local addition.
5Does the ignore list affect the output of git log -p or git show?
No, those commands still show the commit in full. The ignore list only affects the line-by-line attribution in git blame and the annotate views derived from it in editors and web interfaces.
6Should I retroactively add very old formatting commits?
It is worth it as long as the commit hash is still known and present in the repository, since the file works retroactively across the whole history. For very old, frequently overwritten areas, though, the benefit is smaller than for recent formatting changes.
7How do I handle a commit that contains both formatting AND a real bug fix line?
Such mixed commits should be avoided where possible by keeping formatting and functional changes in separate commits. If a commit is already mixed, it should not be added to the ignore list, since that would also hide the functional change in the blame history.
8Does the ignore file also work for git blame via the GitHub REST or GraphQL API?
No, GitHub's official APIs currently do not honor the file, only the web interface itself evaluates it. Anyone fetching blame data programmatically has to apply the ignore list themselves.
9Can I use the file for squash-merged feature branches too?
Yes, once the squash merge commit exists in the target branch, its hash can be added to the file normally, for example when an entire feature is later recognized as a pure formatting change.
10Is there a limit on the number of entries in .git-blame-ignore-revs?
There is no technical limit; the file can hold any number of lines. In practice it stays manageable for most projects, since large formatting commits are rare, deliberate events.