Pragmatism in the real world

Ignoring mass reformatting commits with git blame

I’ve recently merged a PR by Stephen to rst2df that reformats the entire codebase to align with PEP 8. As rst2pdf is over a decade old, this has resulted in a lot of changes to the files which now have Stephen’s name attached. This affects git blame.

For example:

git blame -L 137,145 rst2pdf/findfonts.py
e753c71eb (roberto.alsina   2008-09-05 14:49:24 +0000 137)
d56705e4f (roberto.alsina   2009-10-30 15:07:05 +0000 138)             # So now we have a font we know we ca
825ba00bb (Stephen Finucane 2020-06-08 11:06:59 +0100 139)             for n in (
825ba00bb (Stephen Finucane 2020-06-08 11:06:59 +0100 140)                 fontName.lower(),
825ba00bb (Stephen Finucane 2020-06-08 11:06:59 +0100 141)                 fullName.lower(),
825ba00bb (Stephen Finucane 2020-06-08 11:06:59 +0100 142)                 fullName.lower().replace("italic"
825ba00bb (Stephen Finucane 2020-06-08 11:06:59 +0100 143)             ):
4e4158508 (Roberto Alsina   2019-03-13 14:40:55 -0300 144)                 fonts[n] = (afm, pfbList[baseName
e753c71eb (roberto.alsina   2008-09-05 14:49:24 +0000 145)

Stephen is now implicated as having changed this for loop, but all he did was run the tool that reformatted it.

--ignore-rev to the rescue!

Fortunately, this can be resolved by using --ignore-rev. From the docs:

Ignore changes made by the revision when assigning blame, as if the change never happened. Lines that were changed or added by an ignored commit will be blamed on the previous commit that changed that line or nearby lines. This option may be specified multiple times to ignore more than one revision.

Re-running our command ignoring the re-formmatting commit:

git blame --ignore-rev 825ba00b -L 137,145 rst2pdf/findfonts.py
e753c71eb (roberto.alsina 2008-09-05 14:49:24 +0000 137)
d56705e4f (roberto.alsina 2009-10-30 15:07:05 +0000 138)             # So now we have a font we know we ca
a7b78aceb (Roberto Alsina 2019-03-13 14:44:51 -0300 139)             for n in (
a7b78aceb (Roberto Alsina 2019-03-13 14:44:51 -0300 140)                 fontName.lower(),
a7b78aceb (Roberto Alsina 2019-03-13 14:44:51 -0300 141)                 fullName.lower(),
a7b78aceb (Roberto Alsina 2019-03-13 14:44:51 -0300 142)                 fullName.lower().replace("italic"
a7b78aceb (Roberto Alsina 2019-03-13 14:44:51 -0300 143)             ):
4e4158508 (Roberto Alsina 2019-03-13 14:40:55 -0300 144)                 fonts[n] = (afm, pfbList[baseName
e753c71eb (roberto.alsina 2008-09-05 14:49:24 +0000 145)

Now, we can see that the actual change by Roberto was in March 2019, and I particularly like that the formatting update remains so the line numbers continue to match up with my editor.

Magic!

Store the ignored commits in a file

Remembering to type --ignore-rev 825ba00b will get tedious quite quickly and who remembers commit hashes anyway? To help with this, git blame also supports writing the commit hashes into a file and then referencing the file with --ignore-revs-file.

We create .git-blame-ignore-revs like this:

# Run code through black
825ba00bb95c0a9bb801ccb4c6dd74ec7b59b59e

# Resolve various style issues
a6aff6bf8da21a4e6377238ae95b3c9bb941d655

Each commit has to be the full 40 character hash and we’re allowed comments.

We can then ignore all the commits in the file with:

git blame --ignore-revs-file .git-blame-ignore-revs -L 137,145 rst2pdf/findfonts.py

But there’s more!

We can tell git the name of the file and then git blame will always use it:

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

Note that this needs to set per-project as git blame will error if it cannot find the file.

Now, our original command does exactly what we want:

git blame -L 137,145 rst2pdf/findfonts.py
e753c71eb (roberto.alsina   2008-09-05 14:49:24 +0000 137)
d56705e4f (roberto.alsina   2009-10-30 15:07:05 +0000 138)             # So now we have a font we know we ca
825ba00bb (Stephen Finucane 2020-06-08 11:06:59 +0100 139)             for n in (
825ba00bb (Stephen Finucane 2020-06-08 11:06:59 +0100 140)                 fontName.lower(),
825ba00bb (Stephen Finucane 2020-06-08 11:06:59 +0100 141)                 fullName.lower(),
825ba00bb (Stephen Finucane 2020-06-08 11:06:59 +0100 142)                 fullName.lower().replace("italic"
825ba00bb (Stephen Finucane 2020-06-08 11:06:59 +0100 143)             ):
4e4158508 (Roberto Alsina   2019-03-13 14:40:55 -0300 144)                 fonts[n] = (afm, pfbList[baseName
e753c71eb (roberto.alsina   2008-09-05 14:49:24 +0000 145)

Now we never need to think about the mass reformatting ever again!

Truly magic!

7 thoughts on “Ignoring mass reformatting commits with git blame

  1. Is there a way you can make git blame automatically use the ignore revs file without everyone needing to set the config option locally? Like saving that in the remote repository?

    1. Thanks for the post Rob, very useful!

      In case this helps someone else: in my team we use GitLens on VSCode, so I wanted to see how to add the ignore-revs file to it.
      Reading their source, they have a good example on how to do this: just add "gitlens.advanced.blame.customArguments": ["–ignore-revs-file", "name-of-your-file"] to your to .vscode/settings.json

        1. You’re right. Local git scope will have to be .git/config which cannot be checked in.
          A solution employed by commit-lint is to write to that directory as part of a project’s post-install hook, which might be an option to configure ignore-revs

  2. Man, this is so useless if it really has to be set up manually by every developer. I have no idea why would the file not be seen automatically like .gitignore is for example.

Comments are closed.