diff --git a/git-revise.1 b/git-revise.1 index ed1e0f6..d932273 100644 --- a/git-revise.1 +++ b/git-revise.1 @@ -79,6 +79,12 @@ Reset target commit\(aqs author to the current user. .B \-\-ref Working branch to update; defaults to \fBHEAD\fP\&. .UNINDENT +.INDENT 0.0 +.TP +\fB\-\-gpg-sign\fR, \fB\-S\fR, \fB\-\-no\-gpg\-sign +GPG-sign commits. To override the \fIcommit.gpgSign\fR git configuration, use +\fI\-\-no\-gpg\-sign\fR. +.UNINDENT .SS Main modes of operation .INDENT 0.0 .TP @@ -147,6 +153,14 @@ If set to true, imply \fI\%\-\-autosquash\fP whenever \fI\%\-\-interactive\fP is specified. Overridden by \fI\%\-\-no\-autosquash\fP\&. Defaults to false. If not set, the value of \fBrebase.autoSquash\fP is used instead. .UNINDENT +.INDENT 0.0 +.TP +.B revise.gpgSign +If set to true, GPG-sign new commits; defaults to false. This setting +overrides the original git configuration \fBcommit.gpgSign\fR and may be +overridden by the command line options \fI\-\-gpg\-sign\fR and +\fI\-\-no\-gpg\-sign\fR. +.UNINDENT .SH CONFLICT RESOLUTION .sp When a conflict is encountered, \fBgit revise\fP will attempt to resolve diff --git a/gitrevise/odb.py b/gitrevise/odb.py index f5cd36d..18aef0b 100644 --- a/gitrevise/odb.py +++ b/gitrevise/odb.py @@ -17,6 +17,8 @@ Tuple, cast, ) +import subprocess +from sys import stderr, exit from types import TracebackType from pathlib import Path from enum import Enum @@ -134,6 +136,15 @@ class Repository: index: "Index" """current index state""" + sign_commits: False + """sign commits with gpg""" + + key_id: "" + """key ID to be used for commit signing""" + + gpg: "gpg" + """path to GnuPG binary""" + _objects: Dict[int, Dict[Oid, "GitObj"]] _catfile: Popen _tempdir: Optional[TemporaryDirectory] @@ -144,6 +155,8 @@ class Repository: "default_author", "default_committer", "index", + "sign_commits", + "key_id", "_objects", "_catfile", "_tempdir", @@ -162,6 +175,22 @@ def __init__(self, cwd: Optional[Path] = None): self.index = Index(self) + self.sign_commits = self.bool_config( + "revise.gpgSign", default=self.bool_config("commit.gpgSign", default=False) + ) + + self.key_id = self.default_committer.name + if self.default_committer.email: + if self.key_id: + self.key_id += b" " + self.key_id += b"<" + self.default_committer.email + b">" + else: + self.key_id = self.default_committer.email + + self.key_id = self.bool_config("user.signingKey", default=self.key_id) + + self.gpg = self.config("gpg.program", default="gpg") + self._catfile = Popen( ["git", "cat-file", "--batch"], bufsize=-1, @@ -270,8 +299,25 @@ def new_commit( body += b"parent " + parent.oid.hex().encode() + b"\n" body += b"author " + author + b"\n" body += b"committer " + committer + b"\n" - body += b"\n" - body += message + + body_tail = b"\n" + message + + if self.sign_commits: + p = subprocess.run( + [self.gpg, "-bsau", self.key_id], + capture_output=True, + input=body + body_tail, + ) + if not p.stdout: + print(p.stderr.decode(), file=stderr, end="") + print("gpg failed to sign commit", file=stderr) + exit(1) + + body += b"gpgsig " + p.stdout.replace(b"\n", b"\n ") + body = body[:-1] + + body += body_tail + return Commit(self, body) def new_tree(self, entries: Mapping[bytes, "Entry"]) -> "Tree": diff --git a/gitrevise/tui.py b/gitrevise/tui.py index feaa94b..f599c14 100644 --- a/gitrevise/tui.py +++ b/gitrevise/tui.py @@ -88,6 +88,14 @@ def build_parser() -> ArgumentParser: action="store_true", help="interactively cut a commit into two smaller commits", ) + + mode_group = parser.add_mutually_exclusive_group() + mode_group.add_argument( + "--gpg-sign", "-S", action="store_true", default=None, help="GPG sign commits", + ) + index_group.add_argument( + "--no-gpg-sign", action="store_false", help="do not GPG sign commits", + ) return parser @@ -192,6 +200,9 @@ def inner_main(args: Namespace, repo: Repository): if args.patch: repo.git("add", "-p") + if args.gpg_sign != None: + repo.sign_commits = args.gpg_sign + # Create a commit with changes from the index staged = None if not args.no_index: