252

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
  • 86,445
  • 63
  • 260
  • 306
Magnus
  • 4,146
  • 3
  • 22
  • 28
  • 34
    For the record, you can tell git to always sign commits via configuration: `git config commit.gpgsign true`. – ichigolas Oct 23 '18 at 12:47
  • @nicooga I wish your comment had more upvotes so I noticed this earlier. I've had to pull up this question at least half a dozen times, and setting that flag would've saved me a bunch of time. – Michael Ziluck Sep 16 '19 at 21:28
  • 1
    If the commits have already been published, you should not rewrite them for any purpose (except removing accidental data leaks), as this would change their commit IDs. You **don't need** to sign those old commits explicitly, at least not for data integrity purposes. Since each commit contains SHA-1-based IDs of its parents, verifying any single commit will also implicitly verify its entire history via the hash chain. https://superuser.com/questions/1144817/is-it-a-good-idea-to-gpg-sign-old-git-commits – Michael Freidgeim Nov 25 '20 at 14:08

11 Answers11

266
  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.

Shubham Chaudhary
  • 2,947
  • 1
  • 14
  • 6
  • This was just what I needed for what would have been a really tedious situation, thank you! – msouth Jun 13 '17 at 20:40
  • 10
    Oh I wish I had found this sooner. I read so many things, even from GitHub themselves, saying that you can't resign old commits. This proves that completely false! I could have saved hundreds of commits, which I have now squashed. Oh well... thanks for sharing! I made an alias out of this. `resign = "!re() { git rebase --exec 'git commit --amend --no-edit -n -S' -i $1; }; re"` becomes `git resign HASH` – Barry Jul 06 '17 at 21:16
  • 3
    This should be common knowledge! You have done a great service for humanity (no sarc!)! – hopeseekr Feb 03 '18 at 20:25
  • 6
    There's no reason to rebase at all. Just run `git commit --amend --no-edit -n -S`. – Quolonel Questions Mar 06 '18 at 01:51
  • 38
    Doesn’t this change the history, requiring a `git push --force`? – Steve Apr 14 '18 at 17:21
  • 1
    @Steve Yes, requiring force push depends on what commits you change and whether or not they are pushed already. – Shubham Chaudhary Apr 19 '18 at 06:34
  • Use this for fully automated operation: `GIT_EDITOR=true git rebase --exec 'git commit --amend --no-edit -n -S' -i ` – ens Jan 01 '19 at 14:37
  • 8
    @BarryMode people are correct in that you can't resign old commits. Technically this answer does not resign an old commit, but just creates a new commit containing the same content with an additional signature. It is not the same commit in that force push is required. – SOFe Apr 16 '20 at 01:53
  • 1
    Will the second command “ git rebase --exec 'git commit ....” sign ALL commits including those, that have been signed already? Or I can exclude those and apply to not-signed commits only? – Michael Freidgeim Nov 20 '20 at 23:10
  • 1
    I made an alias for git which set the commit date to author date instead of current time. This will still change the hash of the commit, and you will need to force push it if the commit is not local. https://gist.github.com/Sugavanas/bb7c60677d3d9ecc14de90050bc3351d – kks21199 Nov 13 '21 at 23:54
  • 1
    There is no way to change commits in Git. What you get is a new commit with the same changes. It's important to keep that in mind if you're working on a team and other members of your team are using the same branch. – gerrard00 Jun 13 '22 at 17:35
  • git rebase --exec "git commit --amend --no-edit -n -S" -i development for windows – jTiKey Feb 03 '23 at 13:45
  • 1
    When I do this it tells me that the rebase has been successful but I cannot push anything. So my commit remains unsigned – JRsz Mar 22 '23 at 09:40
  • Is the option `-n` necessary? It looks like the result is the same in this context. Also, if you want to do it "automatically", you can probably remove `-i`. – alessionossa Apr 24 '23 at 12:01
74

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
  • 874
  • 6
  • 5
  • 3
    Most of the time signing the most recent commit, or even creating an empty signed commit on top of it will be sufficient to guarantee authenticity. Git commits are a Merkle tree, so modification of any ancestor commit requires rewrite of its descendants, thus invalidating the signature. – gronostaj Jul 22 '20 at 12:38
  • 1
    @gronostaj That is only true though as long as there are no SHA-1 preimage attack is found. This is not the case currently, however git [is moving away from SHA-1 already](https://git-scm.com/docs/hash-function-transition/). – rugk Jan 21 '21 at 13:05
  • 2
    I imagine many of the users coming here are looking to resign all commits away while preserving commit metadata other than the commit SHA after a `git-filter-repo` strips them away. – vhs Mar 04 '22 at 10:58
31

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.

Illuminator
  • 411
  • 4
  • 5
  • I have my home directory as a git repo (for dotfiles). Some programs interactively pick up changes as its rebasing, funny to see the history being replayed live. It's slow enough because signing is slow – Avindra Goolcharan Jan 22 '17 at 01:33
  • I tried this and got error: gpg failed to sign the data fatal: failed to write commit object – Jackie Apr 30 '22 at 16:47
23

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
  • 26,555
  • 15
  • 113
  • 235
GuyPaddock
  • 753
  • 8
  • 7
  • 1
    One can also add `--env-filter 'export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"'` to ensure the commit date is not reset to today's date. – Arjan Dec 27 '20 at 20:42
  • The downside with using the author date as the committer date is that you'll lose the commit date on rebased/cherry-picked/amended commits. – GuyPaddock Feb 20 '21 at 14:49
  • @Arjan it already does not modify the commit date as noted in the answer. – vhs Mar 04 '22 at 11:26
12

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" = "[email protected]" ];
  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
7

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 [email protected] 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\" = \"[email protected]\" ]; 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

Julien Carsique
  • 171
  • 1
  • 4
3

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
  • 139
  • 3
3

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

git rebase HEAD~N --signoff
phuclv
  • 26,555
  • 15
  • 113
  • 235
Fardin Abdi
  • 139
  • 3
  • 14
    Note that `--signoff` is different than **signing** (e.g. when using `git commit -S`). AFAIK signoff just adds a line like `Signed-off-by ` to the commit message. – stian Sep 18 '20 at 07:33
  • More details about the differences are in https://medium.com/@MarkEmeis/git-commit-signoff-vs-signing-9f37ee272b14 – Michael Freidgeim May 05 '22 at 21:32
  • Question about sign-off is here: https://stackoverflow.com/questions/13043357/git-sign-off-previous-commits – Michael Freidgeim May 06 '22 at 22:32
2

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 --no-edit --no-verify -S --reset-author'
Dan Cecile
  • 151
  • 3
  • 1
    Credit: https://stackoverflow.com/questions/67971851/modify-previous-commit-without-changing-timestamp – Dan Cecile May 16 '23 at 03:45
  • If you're pushing to a remote Git server, after running this command, note that you might get a `fatal: refusing to merge unrelated histories` error with any subsequent commits/pull/fetch/merge... That's to be expected — after all, the remote has a different history, full of unsigned commits. There are many ways of dealing with this situation (depending on what solution you prefer) and a reasonably thorough explanation of the error and how to fix it (with graphics!!) is here: https://stackoverflow.com/a/39783462/1035977 – Gwyneth Llewelyn Aug 01 '23 at 14:33
0

If someone still gets trouble: There is good note I tried: https://gist.github.com/whatisor/3b6f9d30eee5789f04cd90039163cb84

  • 2
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jan 18 '23 at 22:14