131

I was wondering how to sign(-s) off previous commits that I have made in the past in git?

CLARIFICATION: Git has confusingly similarly named concepts git commit -s, --signoff (lower case -s) and git commit -S, --gpg-sign (upper case -S).
This question is about signoff(lower case -s).
However some answers here are about gpg-sign(upper case -S), that better discussed in dedicated questions on superUser Can you GPG sign old commits? and stackoverflow Is there a way to gpg sign all previous commits?

Michael Freidgeim
  • 26,542
  • 16
  • 152
  • 170
camelCaseD
  • 2,483
  • 5
  • 29
  • 44
  • check http://schacon.github.com/git/git-commit.html – NullPoiиteя Oct 24 '12 at 05:13
  • possible duplicate of http://stackoverflow.com/questions/1962094/what-is-the-sign-off-feature-in-git-for – NullPoiиteя Oct 24 '12 at 05:13
  • 1
    I wonder if it isn't ironically insecure to do this. By signing you more or less say "I claim the previous commits are safe". But if you have cloned them, etc, and you don't perform a real check on these, you thus state something you cannot check. Imagine a hacker somehow manipulating a commit. Signing however indeed prevents one from tampering with the commit in a later stage. But perhaps you should add this to the message? – Willem Van Onsem Sep 12 '15 at 22:30
  • 1
    https://superuser.com/questions/397149/can-you-gpg-sign-old-commits#comment2417619_1572460: Note that --signoff is different than signing (e.g. when using git commit -S). More details in https://medium.com/@MarkEmeis/git-commit-signoff-vs-signing-9f37ee272b14 – Michael Freidgeim May 05 '22 at 21:24

11 Answers11

181

To signoff the previous commit, use amend option:

git commit --amend --signoff

Since Git 2.13, you can use the --signoff rebase option to specify range of commits to signoff (credits to @sschuberth). Example to signoff last two commits:

git rebase --signoff HEAD~2

To signoff multiple commits using Git prior to version 2.13, use filter-branch and interpret-trailers as suggested by @vonc et. al. Here is what worked for me.

First, configure git to replace the token sign by Signed-off-by. This has to be done only once and is needed in the next step.

git config trailer.sign.key "Signed-off-by"

The command git filter-branch with the switch --msg-filter will eval the filter once for each commit. The filter can be any shell command that receives the commit message on stdin and outputs on stdout. You can write your own filter, or use git interpret-trailers, which is indepotent. Here is an example that will signoff the latest two commits of the current branch using the current user and email:

export SIGNOFF="sign: $(git config --get user.name) <$(git config --get user.email)>"
git filter-branch -f --msg-filter \
    "git interpret-trailers --trailer \"$SIGNOFF\"" \
     HEAD~2..HEAD

Note 1) Modifying commit messages change the commit id, which means pushing over already published branches will have to be forced either with --force or better --force-with-lease.

Note 2) if you intend to write your custom script, beware that git filter-branch changes the current directory to <repo>/.git-rewrite/t. Using a relative path to the script won't usually work. Instead, the script should be in your $PATH or provided as an absolute path.

fgiraldeau
  • 2,384
  • 1
  • 19
  • 12
63

These days (starting with Git 2.13) you can generally do something like

git rebase --signoff HEAD~2

to add Signed-off-by footers to the last 2 commits (in this example).

If your range includes the root commit, add the --root option to rebase.

sschuberth
  • 28,386
  • 6
  • 101
  • 146
  • I just tested it and this had the same effect as the other suggested answers. To signoff every commit, replace HEAD~2 with the hash of the first commit. – fantom Apr 13 '21 at 02:53
  • 4
    This should somehow get to the top – Yaniv K. Apr 25 '21 at 06:34
  • 4
    To sign commits to, to be 100% verifiable, do `git rebase --signoff -S HEAD~2` – Dzmitry Lahoda Feb 01 '22 at 09:39
  • "replace HEAD~2 with the hash of the first commit" not quite @fantom, replace with the hash of the **parent** of of the first commit. The parameter is the "base" commit and it won't be altered. – tuxayo Aug 05 '22 at 02:11
  • this worked perfectly, followed by `git push -f`. – Triguna Oct 03 '22 at 15:54
34

NOTE: the answer is about gpg-sign(upper case -S), that better discussed in dedicated questions on superUser Can you GPG sign old commits? and stackoverflow Is there a way to gpg sign all previous commits?

Try this one to redo old commits with a -S:

git filter-branch -f --commit-filter 'git commit-tree -S "$@"' HEAD

After that, you have to git push -f. But be careful, the commit ids will change and other people will become out of sync.

Michael Freidgeim
  • 26,542
  • 16
  • 152
  • 170
  • 1
    This will sign off ALL commits, right? How do I sign off only the last X commits? – Ákos Vandra-Meyer Feb 11 '17 at 13:48
  • 18
    @ÁkosVandra if you're still looking for an answer: ``git filter-branch -f --commit-filter 'git commit-tree -S "$@"' HEAD~X..HEAD``, where X is the number of last X commits. e.g. for last 2 commits: ``git filter-branch -f --commit-filter 'git commit-tree -S "$@"' HEAD~2..HEAD`` – Jashandeep Sohi Apr 25 '17 at 04:26
  • 2
    What about doing so only for MY commits, not all? – kerner1000 Mar 07 '19 at 11:34
  • 5
    I think there is a slight confusion, capital `-S` is for GPG-sign commit, I think that the OP meant lower `-s` to simply add `Sign-off-by` line at the end of the commit message. – fgiraldeau Jun 10 '19 at 20:16
30

If anyone still looking for a better-automated way of signing off commits.

Try this:

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

This will rebase everything till the commit-hash (X commits)

Then git push -f is required to push back the change in history back to remote

Keshan Nageswaran
  • 8,060
  • 3
  • 28
  • 45
  • 3
    For more granular approach, first run interactive `git rebase -i HEAD~x`. Then, in editor, "inject" `x git commit -S -s --amend --no-edit` after each picked commit you'd like to mess with. Proposed solution in this answer is probably the cleanest (and most `git`) way to go with this problem. +1 – Vexy Apr 06 '20 at 13:36
  • 2
    The best and better answer I love than the select one. : ) – tai271828 Oct 26 '20 at 22:28
  • 1
    This was smooth as cream. Thanks Keshan! One point is to be noted that this rebases till the `commit-hash` excluding the `commit-hash` itself. – borz Jul 01 '21 at 06:17
  • Try with additionally specifying `--root`. – sschuberth Sep 25 '21 at 07:56
15

For me just ammending signof, didn't actually verify my commits on github.

The solution that is worked for me is going back, and then sign each commit with -S

git commit --amend -S

Also if you check if your commit is actually signed, and your email/name is simply not appended, use this command

git show HEAD --show-signature

Extra tip: If you are already amending your commits, you may want your real name in them (see using git log). You may be using your github handle name, which is not needed. Only correct email is needed and in field of username you should use your full name and github will track it correctly with your github handle name. So to correct your user name and sign last commit use:

git commit --amend --author="FULL NAME <email>" -S

and also set full name for user name in future by

git config --global user.name "FULL NAME"
Varun Garg
  • 2,464
  • 23
  • 37
12

Considering sign-offs modify the commit message , uses git filter-branch to achieve that.

git filter-branch --msg-filter \
    "cat - && echo && echo 'Signed-off-by: Dan McGee <email@example.com>'" \
    HEAD

(example from "git filter-branch magic")

Or, following Curt J. Sampson's suggestion, using git interpret-trailers:

git config trailer.sign.key "Signed-off-by"
git filter-branch --msg-filter \
    "cat - && echo && git interpret-trailers --trailer 'sign: 'Signed-off-by: Dan McGee <email@example.com>'" \
    HEAD

caveat: this will change the SHA1 of your existing commits, and you might have to force push the result, which can be problematic if your commits are already shared by others.

vorburger adds in the comment an example:

Using git version 2.20.1, I had to omit "Signed-off-by" in --trailer 'sign:, and do it like this:

git filter-branch --msg-filter \
  "cat - && echo && git interpret-trailers --trailer 'sign: Michael Vorburger <vorburger@redhat.com>'" \
  HEAD
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • Consider using `git interpret-trailers` with `git filter-branch --msg-filter` rather than adding the `Signed-off-by:` or other trailers by hand. This will let you, e.g., avoid duplicating trailers. – cjs Sep 05 '17 at 12:14
  • @CurtJ.Sampson Yes! Thank you. I was literally documenting that yesterday: https://stackoverflow.com/a/41361273/6309. – VonC Sep 05 '17 at 12:19
  • @CurtJ.Sampson I have amended the answer accordingly. – VonC Sep 05 '17 at 12:27
  • Using git version 2.20.1, I had to omit "'Signed-off-by" in --trailer 'sign: and do it like this: `git filter-branch --msg-filter \ "cat - && echo && git interpret-trailers --trailer 'sign: Michael Vorburger '" \ HEAD` – vorburger Apr 08 '19 at 10:35
  • @vorburger Thank you. I have included your comment in the answer for more visibility. – VonC Apr 08 '19 at 10:52
  • 1
    I had to remove the command prefix `cat - && echo &&`, otherwise it fails with `command not found` error. – fgiraldeau Jun 10 '19 at 20:18
  • @fgiraldeau OK, strange: which Git version and OS are you using? – VonC Jun 10 '19 at 20:33
  • @vonc, git version 2.21 (Ubuntu 16.04). Actually, this seems a shell problem with the line continuation. The extra `cat - && echo &&` are not necessary, but not the cause of the issue. Ho gosh, just found it: there is a space after the backslash on the first command line, which means copy-pasting the command won't work directly ;-) – fgiraldeau Jun 10 '19 at 23:03
12

An interactive rebase with the -S flag will do the job.

Let's say you need to sign off the last n commits (make sure to checkout the latest of those n commits).

Run:

$ git rebase -S -i HEAD~n

# The `-S` flag is important.
# It tells Git to sign the following commits.

This gives a list of the last n commits.

Now, change pick to edit prefix for all the commits you want to sign.

Once done, close the editor. A new editor will open with everything about the commit.

Since nothing needs to be changed in the commit, save the file and exit the editor. You can also change the commit message while at it.

Repeat this for other commits.

To push the latest history, git push remote branch -f.

Warning

There's one gotcha - it can rewrite your commits.

If you sign a 4-month old commit, it might overwrite its date and make it look like it was created today. So, not recommended when you want to preserve your commit history.

roshnet
  • 1,695
  • 18
  • 22
6

I had a similar issue. Here, thanks to Robin Johnson from Gentoo Linux is a trick to add the signature to all my previous unpushed commits:

$ git pull && git rebase --gpg-sign --force-rebase origin/master && git push --signed
Already up-to-date.
Current branch master is up to date, rebase forced.
First, rewinding head to replay your work on top of it...
Applying: sci-biology/KING: new package
Applying: dev-lang/yaggo: version bump, fix install procedure
Applying: sci-libs/htslib: version bump
Applying: sci-biology/bcftools: version bump
Applying: sci-biology/samtools: version bump
Applying: sci-biology/libBigWig: new release with io.h renamed to bigWigIO.h
Applying: sci-biology/MaSuRCA: add more URLs to HOMEPAGE
Applying: sci-biology/SPAdes: update comments on bundled dev-libs/boost
Applying: sci-biology/khmer: added a comment how to proceed with src_compile()
Applying: sci-biology/picard: version bump
Applying: sci-biology/ruffus: pint EGIT_REPO_URI to the archive URL of code.google.com
Applying: sci-biology/vcftools: the 0.1.15_pre release was just renamed to 0.1.15 by upstream
Applying: sci-biology/nanopolish: new package
Applying: sci-biology/libBigWig: version bump
Counting objects: 75, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (75/75), done.
Writing objects: 100% (75/75), 14.51 KiB | 0 bytes/s, done.
Total 75 (delta 55), reused 0 (delta 0)
remote: To github.com:gentoo/sci.git
remote:    29c5e3f5d..b37457700  master -> master
To git+ssh://git.gentoo.org/proj/sci.git
   29c5e3f5d..b37457700  master -> master
$
Martin
  • 79
  • 1
  • 3
3

A quick solution to signoff last X number of commits.

git rebase --signoff @~X

For example, signoff last 10 commits

git rebase --signoff @~10

I found this an easy solution for my case. Source: https://pmhahn.github.io/git-signoff/

0x00A5
  • 1,462
  • 1
  • 16
  • 20
3

A workaround that does not rewrite history:

  1. create a new branch
  2. merge from the old one with flags --no-commit --no-ff
  3. git reset to remove all the commits (signed or unsigned)
  4. git commit -S -am "commit message"
  5. push the new branch with just one SIGNED commit
git status
  ...
  On branch feature/branch_unsigned_commits
  ...
git checkout -b feature/branch_unsigned_commits_take2
git merge --no-commit --no-ff feature/branch_unsigned_commits
git reset
git commit -S -am "commit message"
git push
obotezat
  • 1,041
  • 16
  • 20
0

Sign all unsigned commits till a particular commit hash -

  1. Find the earliest unsigned commit (commit-hash) using -
    git log --show-signature
  2. Run command -
    git rebase --exec 'git commit --amend --no-edit -n -S' -i commit-hash
  3. In rebase interactive shell press - :x
  4. This will sign every commit from most recent to right before the commit-hash
  5. (Optional) Resolve conflicts if any (git add <filename> => git commit -S -m "message" => git rebase --continue))
  6. Force push the changes to your git origin -
    git push -f
vvv444
  • 2,764
  • 1
  • 14
  • 25