Rewrite and sign past Git commits with handy one-liners
- Git
- GPG
- SSH
Recently I encountered a situation where I needed to rewrite the email from all past commits I made on a particular git repository. I used the following script:
#!/bin/sh
git filter-branch --env-filter 'if [ "$GIT_AUTHOR_EMAIL" = "incorrect@email" ]; then
GIT_AUTHOR_EMAIL=correct@email;
GIT_AUTHOR_NAME="Correct Name"
GIT_COMMITTER_EMAIL=$GIT_AUTHOR_EMAIL;
GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; fi' -- --all'
It worked flawlessy, but I noticed that all changed commits were now unsigned. Fortunately, there's a simple one-liner for that, which also uses git-filter-branch
under the hood:
#!/bin/sh
git filter-branch --commit-filter '
if [ "$GIT_COMMIT" != "" ];
then
git commit-tree -S "$@";
else
git commit-tree "$@";
fi' HEAD
You can run it directly, or save into a shell script and run it from there. It will sign all commits in the current branch. If you want to sign all commits in all branches, you can use --all
instead of HEAD
.
Since you've now rewritten the history, you'll need to force push the changes to the remote repository:
git push --force
Before you do that though, it may be worth checking if the commits have actually been signed and that a GPG/SSH signature is correct. You can run git log
command with the following options:
git log --pretty=format:"%h %G? %aN <%aE> %s" -n 10
You may get a following message next to each commit:
error: gpg.ssh.allowedSignersFile needs to be configured and exist for ssh signature verification
It means that git doesn't know what signatures are valid yet. Find you signature (e.g. from ~/.gitconfig
), create a new file, like ~/allowed_signers
and add your copied signature there. Then run the following to tell git where to find it:
git config --global gpg.ssh.allowedSignersFile ~/allowed_signers
Let's now run the git log --pretty=format:"%h %G? %aN <%aE> %s" -n 10
command again. For reference, here's an explanation of each placeholder used in the format string:
%h
: Abbreviated commit hash (usually the first 7 characters of the full commit hash)
%G?
: Signature status of the commit (see below for a detailed explanation of the possible values)
%aN
: Author name of the commit
<%aE>
: Author email of the commit, enclosed within angle brackets <>
%s
: Commit message (subject line)
Regarding the %G?
placeholder, it shows the GPG/SSH signature status of each commit with the following possible values:
G
: Good signature (signed and valid)
B
: Bad signature (signed but invalid)
U
: Untrusted signature (signed but not enough trust)
X
: Expired signature (signed but signature has expired)
Y
: Expiring signature (signed but signature will expire soon)
R
: Revoked signature (signed but signature has been revoked)
E
: Couldn't be checked (signature exists but an error occurred)
N
: No signature
If everything looks good, force push to the remote repository. (Just remember that force-pushing can cause problems for collaborators who have already fetched or cloned the repository. Make sure to inform them of the changes and have them re-clone or rebase their local copies.)
PS. If you liked this article, please share to spread the word.