Welcome to this year’s annual blog post!
I’ve been signing
git commits for my dotfiles repository since its inception in October of last year, so I was excited to see that GitHub recently added GPG signature verification. All you have to do is upload your public key to GitHub and you’ll be verifying commits like a champ. Or so I thought…
GitHub thinks I’m unverified. I think that’s some baloney. I know the public key I uploaded matches the private key I used to sign those commits. Oh, it looks like what they’re really concerned about though is that the email on my PGP key doesn’t match the email I used with
Now, before we go any further I should point out I wouldn’t be having any problems at all if my
user.email matched my GPG key to begin with, but hindsight is 20/20. As it stands, I have a problem and I need to fix it. I need to modify the authorship of these commits to match the email in my GPG key.
I recently learned that
git has a tool called
git filter-branch that can be used to make significant and otherwise tedious modifications to
git history. A quick trip to Stack Overflow reveals I can use this tool to change the authorship of all the commits in my repository.
Reckless reading leads me to this potential solution.
git filter-branch -f --env-filter "GIT_AUTHOR_EMAILemail@example.com'" HEAD
Oh. No. Something is clearly wrong. Looks more like
To be honest, I half expected something like this to happen. With the previous command I was never asked to resign these commits. The text of my commit message has been clobbered with the PGP signature. How do I fix that?
Additional Stack Overflowing (yes, that’s a verb) indicates I can use
--commit-filter options to strip the PGP signature from the commit message and then resign each commit. This ends up looking like
git filter-branch -f --env-filter "GIT_AUTHOR_EMAILfirstname.lastname@example.org'" --msg-filter 'sed "/iQIcBA.*/,/.*END PGP SIGNATURE.*/d"' --commit-filter 'git commit-tree -S "$@"' HEAD
--msg-filter uses a
sed expression to match and delete the PGP signature up to the
END PGP SIGNATURE bit. This leaves the rest of the commit object (basically just the message) intact. The modified object is then passed to the
git commit-tree in the
--commit-filter which then requires me to resign the commit.
Annoyingly, when I actually ran this command I had to sign each and every commit even though I had a
gpg-agent running. If anyone can tell me how to avoid that in the future I’d love to know. Luckily it was only 15 commits, but I would find entering a passphrase any more than that rather aggravating.
At this point the unwieldly
git filter-branch incantation has been uttered. Let’s just double-check the modified commits with a quick
Looks good to me! Notice that the email isn’t the only thing that’s changed. The commit hash is also completely different. These aren’t modified commits, they’re new git objects with the author, date, and commit message preserved. In order to get my changes up to GitHub I’ll have to
git push --force origin master to blow away my previous history. Most of the time this is probably a bad idea, but this repository exists just for me, so I feel comfortable taking the sledgehammer approach.
Well, that didn’t change anything. What gives? What am I missing? How does GitHub expect this to work in the first place? New idea. What if I set my author email correctly from the get-go and create an entirely new signed commit?
git config --global user.email email@example.com
Now I think I’m crazy. This works, but why? Something must be different, but what is it? Let’s check the
git log again.
How does that look any different from the commit messages above that didn’t work? It doesn’t appear to be any different, so let’s take a deeper look. Borrowing from something I learned while reading the excellent Git Immersion tutorial I made use of
git cat-file to inspect the commit objects. Running
git cat-file -p 87051d6 shows the commit object for a commit that GitHub won’t verify.
git cat-file -p 8f08abc shows the commit object for a commit GitHub will verify.
Whoa. While this may have already been obvious to some of you I had no idea that git objects had separate authors and committers. GitHub was right all along. The email in my signature doesn’t match the committer email. I’ll bet I can leverage
git filter-branch again to finally fix this.
Just as there is a
GIT_AUTHOR_EMAIL environment variable to use in a filter, there is also a
GIT_COMMITTER_EMAIL. Now I can simply
git filter-branch -f --env-filter "GIT_AUTHOR_EMAILfirstname.lastname@example.org'; GIT_COMMITTER_EMAILemail@example.com'" --msg-filter 'sed "/iQIcBA.*/,/.*END PGP SIGNATURE.*/d"' --commit-filter 'git commit-tree -S "$@"' HEAD
git cat-file -p fd23e7c shows a commit object with the correct author and commiter.
Unfortunately, now the graph of my
git log looks weird. I have double the commits!
For the third time Stack Overflow saves my bacon and
git update-ref -d refs/original/refs/heads/master
cleans up my graph. Now I can force push to
origin master again and everything will be right again.