298

Git now has the ability to sign commits with git commit -S, which is great, but sometimes I forget the flag to commit, and sometimes I mail myself patches which I apply with am, and that command doesn't have a flag for signing.

Is there a way to add a signature to an already recorded commit?

Oliver Salzburg
  • 89,072
  • 65
  • 269
  • 311
Magnus
  • 4,696

12 Answers12

324
  1. Go into interactive rebase mode.

  2. Add the following line after each commit you want to sign

    exec git commit --amend --no-edit -S

This will run this command after picking each commit.

UPDATE:

Easier way to do this is:

git rebase --exec 'git commit --amend --no-edit -n -S' -i development

This rebases everything till development (or any hash) and you don't have to copy paste after every commit.

82

Signing a commit changes its contents, so more recent commits depending on it will change their hash.

If you just want to sign the most recent commit, git commit -S --amend will work.

paxswill
  • 954
37

I use git rebase -i --root ( see Rewriting History ) and change pick to edit.

Then I use git commit -S --amend --no-edit && git rebase --continue (on Windows) for each commits.

This is manually sign for each commits. I hope we will found better solution.

29

If you need to GPG sign all commits SINCE a particular commit on the current branch, you can use the following instead:

git filter-branch --commit-filter 'git commit-tree -S "$@";' <COMMIT>..HEAD

Where <COMMIT> is the commit id (e.g. abc123e5).

This has the added benefit that it does not disturb the commit metadata (including commit date). The commit hashes will change, though (since it's a digest of the contents of each commit, and a signature is being added to each commit).

If you also would like to stop getting prompted for your GPG passphrase on every commit, also see this answer: https://askubuntu.com/a/805550

NOTE: Switching from gpg to gpg2 for GIT signing will require you to re-import your private key in GPG 2.

phuclv
  • 30,396
  • 15
  • 136
  • 260
13

If you want to filter only specific commits and sign only them you can use filter-branch:

git filter-branch --commit-filter 'if [ "$GIT_COMMITTER_EMAIL" = "user@domain.com" ];
  then git commit-tree -S "$@";
  else git commit-tree "$@";
  fi' HEAD

This is useful if, for some reason, you want to sign only your own commits.

12

I also stumbled on the same problem and here is my solution:

git rebase -i --root --exec 'git commit --amend --no-edit --no-verify -S'

this will sign all of my commits from the first initial commit and also bypass commit hook that I set up using husky. No need to change pick to edit.

DrSensor
  • 229
  • 2
  • 3
8

If no filtering on commit is needed, then it is preferred to use rebase than filter-branch:

git rebase -i master --exec 'git commit --amend --no-edit --no-verify -S --reset-author'

Else, you can leave untouched the commits you don't own.
Set the following alias in ~/.gitconfig (replace your@address.com with your email address):

resign = "!_() { : git checkout ; [ \"$#\" -eq 0 ] && echo 'Usage: resign <rev-list>' && exit 2; \
                   git filter-branch --commit-filter ' \
                   if [ \"$GIT_COMMITTER_EMAIL\" = \"your@address.com\" ]; then git commit-tree -S \"$@\"; else git commit-tree \"$@\"; fi' $1; }; _"

Then for instance, to resign all your commits in the current branch pulled from master, do:

git resign master..

Credits to previous answers by BarryMode and Roberto Leinardi

4

here's the one I use for all commits, yes it will re-write history:

git rebase --exec 'git commit --amend --no-edit -n -S' -i --root
Zia
  • 149
4

Here's how to re-sign all of the commits on current branch while preserving original author and timestamp:

git rebase -i --root --exec 'env GIT_AUTHOR_DATE="$(git log --no-walk --format=%ad)" GIT_COMMITTER_DATE="$(git log --no-walk --format=%cd)" git commit --amend --allow-empty --no-edit --no-verify -S --reset-author'

PowerShell version of the above for Windows users:

git rebase -i --root --exec '$env:GIT_AUTHOR_DATE="$(git log --no-walk --format=%ad)"; $env:GIT_COMMITTER_DATE="$(git log --no-walk --format=%cd)"; git commit --amend --allow-empty --no-edit --no-verify -S --reset-author'

What this does:

  • git rebase -i --root --exec: Starts an interactive rebase starting from the root commit (the first commit) of your current branch. The --exec option allows us to specify a shell command to run after each commit.
  • git log --no-walk --format=%ad is the author date (%ad) which we assign to GIT_AUTHOR_DATE
  • git log --no-walk --format=%cd is the committer date (%cd) which we assign to GIT_COMMITTER_DATE
  • We then amend the current commit with those dates, thereby re-signing the commit
    • --allow-empty allows commits with no changes,
    • --no-edit uses the existing commit message,
    • --no-verify skips pre-commit and commit-msg hooks,
    • -S signs the commit, and --reset-author updates the author date and time.

Finally, you may need to run:

(git pull --allow-unrelated-histories || git merge --strategy-option ours --allow-unrelated-histories) && git push

In case you have un-pushed local commits, you may need to merge before pushing, hence the git merge fallback in the above combo.

Dan Cecile
  • 171
  • 3
3

To sign off last N commits, you can also do:

git rebase HEAD~N --signoff
phuclv
  • 30,396
  • 15
  • 136
  • 260
1

One simple solution to sign all commits

git rebase --root --gpg-sign --committer-date-is-author-date

Source: https://rollen.io/blog/resigning-git-commits/

Moulick
  • 111
0

For those not experimented using git, if you need to do it to one specific commit:

  • Locate the commit hash (1234)
  • I knew in my case was the prior to the last one so I used git rebase -i HEAD~2.
  • One screen will show up, locate the hash (1234) and change the word pick to edit.
  • save: Esc then :wq!
  • execute in the terminal git commit --amend --no-edit -S.
  • execute in the terminal git rebase --continue
  • Then push The edited hashes will change! Greetings!