commit: teach --gpg-sign option

This uses the gpg-interface.[ch] to allow signing the commit, i.e. $ git commit --gpg-sign -m foo You need a passphrase to unlock the secret key for user: "Junio C Hamano <gitster@pobox.com>" 4096-bit RSA key, ID 96AFE6CB, created 2011-10-03 (main key ID 713660A7) [master 8457d13] foo 1 files changed, 1 insertions(+), 0 deletions(-) The lines of GPG detached signature are placed in a new multi-line header field, instead of tucking the signature block at the end of the commit log message text (similar to how signed tag is done), for multiple reasons: - The signature won't clutter output from "git log" and friends if it is in the extra header. If we place it at the end of the log message, we would need to teach "git log" and friends to strip the signature block with an option. - Teaching new versions of "git log" and "gitk" to optionally verify and show signatures is cleaner if we structurally know where the signature block is (instead of scanning in the commit log message). - The signature needs to be stripped upon various commit rewriting operations, e.g. rebase, filter-branch, etc. They all already ignore unknown headers, but if we place signature in the log message, all of these tools (and third-party tools) also need to learn how a signature block would look like. - When we added the optional encoding header, all the tools (both in tree and third-party) that acts on the raw commit object should have been fixed to ignore headers they do not understand, so it is not like that new header would be more likely to break than extra text in the commit. A commit made with the above sample sequence would look like this: $ git cat-file commit HEAD tree 3cd71d90e3db4136e5260ab54599791c4f883b9d parent b87755351a47b09cb27d6913e6e0e17e6254a4d4 author Junio C Hamano <gitster@pobox.com> 1317862251 -0700 committer Junio C Hamano <gitster@pobox.com> 1317862251 -0700 gpgsig -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.10 (GNU/Linux) iQIcBAABAgAGBQJOjPtrAAoJELC16IaWr+bL4TMP/RSe2Y/jYnCkds9unO5JEnfG ... =dt98 -----END PGP SIGNATURE----- foo but "git log" (unless you ask for it with --pretty=raw) output is not cluttered with the signature information. Signed-off-by: Junio C Hamano <gitster@pobox.com>

Junio C Hamano committed Oct 5, 2011 at 17:23 UTC ba3c69a9ee1894de397b60d3b548383e13ef49e3
7 files changed +95 -13
builtin/commit-tree.c
+22 -3
@@ -8,8 +8,9 @@
8 #include "tree.h"
9 #include "builtin.h"
10 #include "utf8.h"
11 +#include "gpg-interface.h"
12
12 -static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-m <message>] [-F <file>] <sha1> <changelog";
13 +static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-S<signer>] [-m <message>] [-F <file>] <sha1> <changelog";
14
15 static void new_parent(struct commit *parent, struct commit_list **parents_p)
16 {
@@ -25,6 +26,14 @@ static void new_parent(struct commit *parent, struct commit_list **parents_p)
26 commit_list_insert(parent, parents_p);
27 }
28
29 +static int commit_tree_config(const char *var, const char *value, void *cb)
30 +{
31 + int status = git_gpg_config(var, value, NULL);
32 + if (status)
33 + return status;
34 + return git_default_config(var, value, cb);
35 +}
36 +
37 int cmd_commit_tree(int argc, const char **argv, const char *prefix)
38 {
39 int i, got_tree = 0;
@@ -32,12 +41,16 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
41 unsigned char tree_sha1[20];
42 unsigned char commit_sha1[20];
43 struct strbuf buffer = STRBUF_INIT;
44 + const char *sign_commit = NULL;
45
36 - git_config(git_default_config, NULL);
46 + git_config(commit_tree_config, NULL);
47
48 if (argc < 2 || !strcmp(argv[1], "-h"))
49 usage(commit_tree_usage);
50
51 + if (get_sha1(argv[1], tree_sha1))
52 + die("Not a valid object name %s", argv[1]);
53 +
54 for (i = 1; i < argc; i++) {
55 const char *arg = argv[i];
56 if (!strcmp(arg, "-p")) {
@@ -51,6 +64,11 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
64 continue;
65 }
66
67 + if (!memcmp(arg, "-S", 2)) {
68 + sign_commit = arg + 2;
69 + continue;
70 + }
71 +
72 if (!strcmp(arg, "-m")) {
73 if (argc <= ++i)
74 usage(commit_tree_usage);
@@ -98,7 +116,8 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
116 die_errno("git commit-tree: failed to read");
117 }
118
101 - if (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) {
119 + if (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1,
120 + NULL, sign_commit)) {
121 strbuf_release(&buffer);
122 return 1;
123 }
builtin/commit.c
+10 -1
@@ -26,6 +26,7 @@
26 #include "unpack-trees.h"
27 #include "quote.h"
28 #include "submodule.h"
29 +#include "gpg-interface.h"
30
31 static const char * const builtin_commit_usage[] = {
32 "git commit [options] [--] <filepattern>...",
@@ -85,6 +86,8 @@ static int all, edit_flag, also, interactive, patch_interactive, only, amend, si
86 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
87 static int no_post_rewrite, allow_empty_message;
88 static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
89 +static char *sign_commit;
90 +
91 /*
92 * The default commit message cleanup mode will remove the lines
93 * beginning with # (shell comments) and leading and trailing
@@ -144,6 +147,8 @@ static struct option builtin_commit_options[] = {
147 OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
148 OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
149 OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"),
150 + { OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id",
151 + "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
152 /* end commit message options */
153
154 OPT_GROUP("Commit contents options"),
@@ -1324,6 +1329,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1,
1329 static int git_commit_config(const char *k, const char *v, void *cb)
1330 {
1331 struct wt_status *s = cb;
1332 + int status;
1333
1334 if (!strcmp(k, "commit.template"))
1335 return git_config_pathname(&template_file, k, v);
@@ -1332,6 +1338,9 @@ static int git_commit_config(const char *k, const char *v, void *cb)
1338 return 0;
1339 }
1340
1341 + status = git_gpg_config(k, v, NULL);
1342 + if (status)
1343 + return status;
1344 return git_status_config(k, v, s);
1345 }
1346
@@ -1488,7 +1497,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
1497 extra = read_commit_extra_headers(current_head);
1498
1499 if (commit_tree_extended(sb.buf, active_cache_tree->sha1, parents, sha1,
1491 - author_ident.buf, extra)) {
1500 + author_ident.buf, sign_commit, extra)) {
1501 rollback_index_files();
1502 die(_("failed to write commit object"));
1503 }
builtin/merge.c
+14 -2
@@ -26,6 +26,7 @@
26 #include "merge-recursive.h"
27 #include "resolve-undo.h"
28 #include "remote.h"
29 +#include "gpg-interface.h"
30
31 #define DEFAULT_TWOHEAD (1<<0)
32 #define DEFAULT_OCTOPUS (1<<1)
@@ -62,6 +63,7 @@ static int allow_rerere_auto;
63 static int abort_current_merge;
64 static int show_progress = -1;
65 static int default_to_upstream;
66 +static const char *sign_commit;
67
68 static struct strategy all_strategy[] = {
69 { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL },
@@ -207,6 +209,8 @@ static struct option builtin_merge_options[] = {
209 OPT_BOOLEAN(0, "abort", &abort_current_merge,
210 "abort the current in-progress merge"),
211 OPT_SET_INT(0, "progress", &show_progress, "force progress reporting", 1),
212 + { OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id",
213 + "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
214 OPT_END()
215 };
216
@@ -533,6 +537,8 @@ static void parse_branch_merge_options(char *bmo)
537
538 static int git_merge_config(const char *k, const char *v, void *cb)
539 {
540 + int status;
541 +
542 if (branch && !prefixcmp(k, "branch.") &&
543 !prefixcmp(k + 7, branch) &&
544 !strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
@@ -570,6 +576,10 @@ static int git_merge_config(const char *k, const char *v, void *cb)
576 default_to_upstream = git_config_bool(k, v);
577 return 0;
578 }
579 +
580 + status = git_gpg_config(k, v, NULL);
581 + if (status)
582 + return status;
583 return git_diff_ui_config(k, v, cb);
584 }
585
@@ -902,7 +912,8 @@ static int merge_trivial(struct commit *head)
912 parent->next->item = remoteheads->item;
913 parent->next->next = NULL;
914 prepare_to_commit();
905 - commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
915 + commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL,
916 + sign_commit);
917 finish(head, result_commit, "In-index merge");
918 drop_save();
919 return 0;
@@ -933,7 +944,8 @@ static int finish_automerge(struct commit *head,
944 strbuf_addch(&merge_msg, '\n');
945 prepare_to_commit();
946 free_commit_list(remoteheads);
936 - commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
947 + commit_tree(merge_msg.buf, result_tree, parents, result_commit,
948 + NULL, sign_commit);
949 strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
950 finish(head, result_commit, buf.buf);
951 strbuf_release(&buf);
commit.c
+45 -3
@@ -6,6 +6,7 @@
6 #include "diff.h"
7 #include "revision.h"
8 #include "notes.h"
9 +#include "gpg-interface.h"
10
11 int save_commit_buffer = 1;
12
@@ -840,6 +841,42 @@ struct commit_list *reduce_heads(struct commit_list *heads)
841 return result;
842 }
843
844 +static const char gpg_sig_header[] = "gpgsig";
845 +static const int gpg_sig_header_len = sizeof(gpg_sig_header) - 1;
846 +
847 +static int do_sign_commit(struct strbuf *buf, const char *keyid)
848 +{
849 + struct strbuf sig = STRBUF_INIT;
850 + int inspos, copypos;
851 +
852 + /* find the end of the header */
853 + inspos = strstr(buf->buf, "\n\n") - buf->buf + 1;
854 +
855 + if (!keyid || !*keyid)
856 + keyid = get_signing_key();
857 + if (sign_buffer(buf, &sig, keyid)) {
858 + strbuf_release(&sig);
859 + return -1;
860 + }
861 +
862 + for (copypos = 0; sig.buf[copypos]; ) {
863 + const char *bol = sig.buf + copypos;
864 + const char *eol = strchrnul(bol, '\n');
865 + int len = (eol - bol) + !!*eol;
866 +
867 + if (!copypos) {
868 + strbuf_insert(buf, inspos, gpg_sig_header, gpg_sig_header_len);
869 + inspos += gpg_sig_header_len;
870 + }
871 + strbuf_insert(buf, inspos++, " ", 1);
872 + strbuf_insert(buf, inspos, bol, len);
873 + inspos += len;
874 + copypos += len;
875 + }
876 + strbuf_release(&sig);
877 + return 0;
878 +}
879 +
880 static void handle_signed_tag(struct commit *parent, struct commit_extra_header ***tail)
881 {
882 struct merge_remote_desc *desc;
@@ -975,13 +1012,14 @@ void free_commit_extra_headers(struct commit_extra_header *extra)
1012
1013 int commit_tree(const char *msg, unsigned char *tree,
1014 struct commit_list *parents, unsigned char *ret,
978 - const char *author)
1015 + const char *author, const char *sign_commit)
1016 {
1017 struct commit_extra_header *extra = NULL, **tail = &extra;
1018 int result;
1019
1020 append_merge_tag_headers(parents, &tail);
984 - result = commit_tree_extended(msg, tree, parents, ret, author, extra);
1021 + result = commit_tree_extended(msg, tree, parents, ret,
1022 + author, sign_commit, extra);
1023 free_commit_extra_headers(extra);
1024 return result;
1025 }
@@ -993,7 +1031,8 @@ static const char commit_utf8_warn[] =
1031
1032 int commit_tree_extended(const char *msg, unsigned char *tree,
1033 struct commit_list *parents, unsigned char *ret,
996 - const char *author, struct commit_extra_header *extra)
1034 + const char *author, const char *sign_commit,
1035 + struct commit_extra_header *extra)
1036 {
1037 int result;
1038 int encoding_is_utf8;
@@ -1043,6 +1082,9 @@ int commit_tree_extended(const char *msg, unsigned char *tree,
1082 if (encoding_is_utf8 && !is_utf8(buffer.buf))
1083 fprintf(stderr, commit_utf8_warn);
1084
1085 + if (sign_commit && do_sign_commit(&buffer, sign_commit))
1086 + return -1;
1087 +
1088 result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
1089 strbuf_release(&buffer);
1090 return result;
commit.h
+2 -2
@@ -193,11 +193,11 @@ extern void append_merge_tag_headers(struct commit_list *parents,
193
194 extern int commit_tree(const char *msg, unsigned char *tree,
195 struct commit_list *parents, unsigned char *ret,
196 - const char *author);
196 + const char *author, const char *sign_commit);
197
198 extern int commit_tree_extended(const char *msg, unsigned char *tree,
199 struct commit_list *parents, unsigned char *ret,
200 - const char *author,
200 + const char *author, const char *sign_commit,
201 struct commit_extra_header *);
202
203 extern struct commit_extra_header *read_commit_extra_headers(struct commit *);
notes-cache.c
+1 -1
@@ -56,7 +56,7 @@ int notes_cache_write(struct notes_cache *c)
56
57 if (write_notes_tree(&c->tree, tree_sha1))
58 return -1;
59 - if (commit_tree(c->validity, tree_sha1, NULL, commit_sha1, NULL) < 0)
59 + if (commit_tree(c->validity, tree_sha1, NULL, commit_sha1, NULL, NULL) < 0)
60 return -1;
61 if (update_ref("update notes cache", c->tree.ref, commit_sha1, NULL,
62 0, QUIET_ON_ERR) < 0)
notes-merge.c
+1 -1
@@ -546,7 +546,7 @@ void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
546 /* else: t->ref points to nothing, assume root/orphan commit */
547 }
548
549 - if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL))
549 + if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL, NULL))
550 die("Failed to commit notes tree to database");
551 }
552