{"thread":{"id":"28598","subject":"[PATCH] commit: teach --gpg-sign option","startedAt":"2011-10-06T00:56:55Z","lastAt":"2011-10-22T17:55:21Z","messageCount":35,"participants":["Junio C Hamano","Shawn Pearce","Jonathan Nieder","Matthieu Moy","Michael J Gruber","Robin H. Johnson","Nguyen Thai Ngoc Duy","Elia Pinto"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"176987","messageId":"7vaa9f3pk8.fsf@alter.siamese.dyndns.org","threadId":"28598","inReplyTo":null,"subject":"[PATCH] commit: teach --gpg-sign option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-10-06T00:56:55Z","receivedAt":"2011-10-06T00:56:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"And this uses the gpg-interface.[ch] to allow signing the commit, i.e.\n\n    $ git commit --gpg-sign -m foo\n    You need a passphrase to unlock the secret key for\n    user: \"Junio C Hamano <gitster@pobox.com>\"\n    4096-bit RSA key, ID 96AFE6CB, created 2011-10-03 (main key ID 713660A7)\n\n    [master 8457d13] foo\n     1 files changed, 1 insertions(+), 0 deletions(-)\n\nThe lines of GPG detached signature are placed in new header lines, after\nthe standard tree/parent/author/committer headers, instead of tucking the\nsignature block at the end of the commit log message text (similar to how\nsigned tag is done), for multiple reasons:\n\n - The signature won't clutter output from \"git log\" and friends if it is\n   in the extra header. If we place it at the end of the log message, we\n   would need to teach \"git log\" and friends to strip the signature block\n   with an option.\n\n - Teaching new versions of \"git log\" and \"gitk\" to optionally verify and\n   show signatures is cleaner if we structurally know where the signature\n   block is (instead of scanning in the commit log message).\n\n - The signature needs to be stripped upon various commit rewriting\n   operations, e.g. rebase, filter-branch, etc. They all already ignore\n   unknown headers, but if we place signature in the log message, all of\n   these tools (and third-party tools) also need to learn how a signature\n   block would look like.\n\n - When we added the optional encoding header, all the tools (both in tree\n   and third-party) that acts on the raw commit object should have been\n   fixed to ignore headers they do not understand, so it is not like that\n   new header would be more likely to break than extra text in the commit.\n\nA commit made with the above sample sequence would look like this:\n\n    $ git cat-file commit HEAD\n    tree 3cd71d90e3db4136e5260ab54599791c4f883b9d\n    parent b87755351a47b09cb27d6913e6e0e17e6254a4d4\n    author Junio C Hamano <gitster@pobox.com> 1317862251 -0700\n    committer Junio C Hamano <gitster@pobox.com> 1317862251 -0700\n    sig -----BEGIN PGP SIGNATURE-----\n    sig Version: GnuPG v1.4.10 (GNU/Linux)\n    sig\n    sig iQIcBAABAgAGBQJOjPtrAAoJELC16IaWr+bL4TMP/RSe2Y/jYnCkds9unO5JEnfG\n    sig ...\n    sig =dt98\n    sig -----END PGP SIGNATURE-----\n\n    foo\n\nbut \"git log\" (unless you ask for it with --pretty=raw) output is not\ncluttered with the signature information.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n\nCf.\n    Message-ID: <7vfwjgui8s.fsf_-_@alter.siamese.dyndns.org>\n    http://thread.gmane.org/gmane.comp.version-control.git/182297/focus=182384\n\n builtin/commit-tree.c |   24 +++++++++++++++++++++---\n builtin/commit.c      |   12 ++++++++++--\n builtin/merge.c       |   16 ++++++++++++++--\n commit.c              |   41 ++++++++++++++++++++++++++++++++++++++++-\n commit.h              |    2 +-\n notes-cache.c         |    2 +-\n notes-merge.c         |    2 +-\n 7 files changed, 88 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/commit-tree.c b/builtin/commit-tree.c\nindex d083795..a17811f 100644\n--- a/builtin/commit-tree.c\n+++ b/builtin/commit-tree.c\n@@ -8,8 +8,9 @@\n #include \"tree.h\"\n #include \"builtin.h\"\n #include \"utf8.h\"\n+#include \"gpg-interface.h\"\n \n-static const char commit_tree_usage[] = \"git commit-tree <sha1> [(-p <sha1>)...] < changelog\";\n+static const char commit_tree_usage[] = \"git commit-tree [-S<signer>] <sha1> [(-p <sha1>)...] < changelog\";\n \n static void new_parent(struct commit *parent, struct commit_list **parents_p)\n {\n@@ -25,6 +26,14 @@ static void new_parent(struct commit *parent, struct commit_list **parents_p)\n \tcommit_list_insert(parent, parents_p);\n }\n \n+static int commit_tree_config(const char *var, const char *value, void *cb)\n+{\n+\tint status = git_gpg_config(var, value, NULL);\n+\tif (status)\n+\t\treturn status;\n+\treturn git_default_config(var, value, cb);\n+}\n+\n int cmd_commit_tree(int argc, const char **argv, const char *prefix)\n {\n \tint i;\n@@ -32,11 +41,19 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)\n \tunsigned char tree_sha1[20];\n \tunsigned char commit_sha1[20];\n \tstruct strbuf buffer = STRBUF_INIT;\n+\tconst char *sign_commit = NULL;\n \n-\tgit_config(git_default_config, NULL);\n+\tgit_config(commit_tree_config, NULL);\n \n \tif (argc < 2 || !strcmp(argv[1], \"-h\"))\n \t\tusage(commit_tree_usage);\n+\n+\tif (!memcmp(argv[1], \"-S\", 2)) {\n+\t\tsign_commit = argv[1] + 2;\n+\t\targv++;\n+\t\targc--;\n+\t}\n+\n \tif (get_sha1(argv[1], tree_sha1))\n \t\tdie(\"Not a valid object name %s\", argv[1]);\n \n@@ -56,7 +73,8 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)\n \tif (strbuf_read(&buffer, 0, 0) < 0)\n \t\tdie_errno(\"git commit-tree: failed to read\");\n \n-\tif (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) {\n+\tif (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1,\n+\t\t\tNULL, sign_commit)) {\n \t\tstrbuf_release(&buffer);\n \t\treturn 1;\n \t}\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex cbc9613..90cf7e8 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -26,6 +26,7 @@\n #include \"unpack-trees.h\"\n #include \"quote.h\"\n #include \"submodule.h\"\n+#include \"gpg-interface.h\"\n \n static const char * const builtin_commit_usage[] = {\n \t\"git commit [options] [--] <filepattern>...\",\n@@ -85,6 +86,8 @@ static int all, edit_flag, also, interactive, patch_interactive, only, amend, si\n static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;\n static int no_post_rewrite, allow_empty_message;\n static char *untracked_files_arg, *force_date, *ignore_submodule_arg;\n+static char *sign_commit;\n+\n /*\n  * The default commit message cleanup mode will remove the lines\n  * beginning with # (shell comments) and leading and trailing\n@@ -144,6 +147,8 @@ static struct option builtin_commit_options[] = {\n \tOPT_BOOLEAN('e', \"edit\", &edit_flag, \"force edit of commit\"),\n \tOPT_STRING(0, \"cleanup\", &cleanup_arg, \"default\", \"how to strip spaces and #comments from message\"),\n \tOPT_BOOLEAN(0, \"status\", &include_status, \"include status in commit message template\"),\n+\t{ OPTION_STRING, 'S', \"gpg-sign\", &sign_commit, \"key id\",\n+\t  \"GPG sign commit\", PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n \t/* end commit message options */\n \n \tOPT_GROUP(\"Commit contents options\"),\n@@ -1323,6 +1328,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1,\n static int git_commit_config(const char *k, const char *v, void *cb)\n {\n \tstruct wt_status *s = cb;\n+\tint status;\n \n \tif (!strcmp(k, \"commit.template\"))\n \t\treturn git_config_pathname(&template_file, k, v);\n@@ -1330,7 +1336,9 @@ static int git_commit_config(const char *k, const char *v, void *cb)\n \t\tinclude_status = git_config_bool(k, v);\n \t\treturn 0;\n \t}\n-\n+\tstatus = git_gpg_config(k, v, NULL);\n+\tif (status)\n+\t\treturn status;\n \treturn git_status_config(k, v, s);\n }\n \n@@ -1481,7 +1489,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t}\n \n \tif (commit_tree(sb.buf, active_cache_tree->sha1, parents, sha1,\n-\t\t\tauthor_ident.buf)) {\n+\t\t\tauthor_ident.buf, sign_commit)) {\n \t\trollback_index_files();\n \t\tdie(_(\"failed to write commit object\"));\n \t}\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex ab4077f..53cff02 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -26,6 +26,7 @@\n #include \"merge-recursive.h\"\n #include \"resolve-undo.h\"\n #include \"remote.h\"\n+#include \"gpg-interface.h\"\n \n #define DEFAULT_TWOHEAD (1<<0)\n #define DEFAULT_OCTOPUS (1<<1)\n@@ -63,6 +64,7 @@ static int allow_rerere_auto;\n static int abort_current_merge;\n static int show_progress = -1;\n static int default_to_upstream;\n+static const char *sign_commit;\n \n static struct strategy all_strategy[] = {\n \t{ \"recursive\",  DEFAULT_TWOHEAD | NO_TRIVIAL },\n@@ -206,6 +208,8 @@ static struct option builtin_merge_options[] = {\n \tOPT_BOOLEAN(0, \"abort\", &abort_current_merge,\n \t\t\"abort the current in-progress merge\"),\n \tOPT_SET_INT(0, \"progress\", &show_progress, \"force progress reporting\", 1),\n+\t{ OPTION_STRING, 'S', \"gpg-sign\", &sign_commit, \"key id\",\n+\t  \"GPG sign commit\", PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n \tOPT_END()\n };\n \n@@ -525,6 +529,8 @@ static void parse_branch_merge_options(char *bmo)\n \n static int git_merge_config(const char *k, const char *v, void *cb)\n {\n+\tint status;\n+\n \tif (branch && !prefixcmp(k, \"branch.\") &&\n \t\t!prefixcmp(k + 7, branch) &&\n \t\t!strcmp(k + 7 + strlen(branch), \".mergeoptions\")) {\n@@ -562,6 +568,10 @@ static int git_merge_config(const char *k, const char *v, void *cb)\n \t\tdefault_to_upstream = git_config_bool(k, v);\n \t\treturn 0;\n \t}\n+\n+\tstatus = git_gpg_config(k, v, NULL);\n+\tif (status)\n+\t\treturn status;\n \treturn git_diff_ui_config(k, v, cb);\n }\n \n@@ -870,7 +880,8 @@ static int merge_trivial(void)\n \tparent->next->item = remoteheads->item;\n \tparent->next->next = NULL;\n \trun_prepare_commit_msg();\n-\tcommit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);\n+\tcommit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL,\n+\t\t    sign_commit);\n \tfinish(result_commit, \"In-index merge\");\n \tdrop_save();\n \treturn 0;\n@@ -900,7 +911,8 @@ static int finish_automerge(struct commit_list *common,\n \tfree_commit_list(remoteheads);\n \tstrbuf_addch(&merge_msg, '\\n');\n \trun_prepare_commit_msg();\n-\tcommit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);\n+\tcommit_tree(merge_msg.buf, result_tree, parents, result_commit,\n+\t\t    NULL, sign_commit);\n \tstrbuf_addf(&buf, \"Merge made by the '%s' strategy.\", wt_strategy);\n \tfinish(result_commit, buf.buf);\n \tstrbuf_release(&buf);\ndiff --git a/commit.c b/commit.c\nindex 97b4327..969435d 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -6,6 +6,7 @@\n #include \"diff.h\"\n #include \"revision.h\"\n #include \"notes.h\"\n+#include \"gpg-interface.h\"\n \n int save_commit_buffer = 1;\n \n@@ -814,6 +815,41 @@ struct commit_list *reduce_heads(struct commit_list *heads)\n \treturn result;\n }\n \n+static int do_sign_commit(struct strbuf *buf, const char *keyid)\n+{\n+\tstruct strbuf sig = STRBUF_INIT;\n+\tint inspos, copypos;\n+\tconst char gpg_sig[] = \"sig \";\n+\tconst int header_len = sizeof(gpg_sig) - 1;\n+\n+\t/* find the end of the header */\n+\tinspos = strstr(buf->buf, \"\\n\\n\") - buf->buf + 1;\n+\tcopypos = buf->len;\n+\n+\tstrbuf_addbuf(&sig, buf);\n+\n+\tif (!keyid || !*keyid)\n+\t\tkeyid = get_signing_key();\n+\tif (sign_buffer(&sig, keyid)) {\n+\t\tstrbuf_release(&sig);\n+\t\treturn -1;\n+\t}\n+\n+\twhile (sig.buf[copypos]) {\n+\t\tconst char *bol = sig.buf + copypos;\n+\t\tconst char *eol = strchrnul(bol, '\\n');\n+\t\tint len = (eol - bol) + !!*eol;\n+\t\tstrbuf_insert(buf, inspos, gpg_sig, header_len);\n+\t\tinspos += header_len;\n+\t\tstrbuf_insert(buf, inspos, bol, len);\n+\t\tinspos += len;\n+\t\tcopypos += len;\n+\t}\n+\tstrbuf_release(&sig);\n+\treturn 0;\n+}\n+\n+\n static const char commit_utf8_warn[] =\n \"Warning: commit message does not conform to UTF-8.\\n\"\n \"You may want to amend it after fixing the message, or set the config\\n\"\n@@ -821,7 +857,7 @@ static const char commit_utf8_warn[] =\n \n int commit_tree(const char *msg, unsigned char *tree,\n \t\tstruct commit_list *parents, unsigned char *ret,\n-\t\tconst char *author)\n+\t\tconst char *author, const char *sign_commit)\n {\n \tint result;\n \tint encoding_is_utf8;\n@@ -864,6 +900,9 @@ int commit_tree(const char *msg, unsigned char *tree,\n \tif (encoding_is_utf8 && !is_utf8(buffer.buf))\n \t\tfprintf(stderr, commit_utf8_warn);\n \n+\tif (sign_commit && do_sign_commit(&buffer, sign_commit))\n+\t\treturn -1;\n+\n \tresult = write_sha1_file(buffer.buf, buffer.len, commit_type, ret);\n \tstrbuf_release(&buffer);\n \treturn result;\ndiff --git a/commit.h b/commit.h\nindex 12d100b8..8c2419b 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -175,6 +175,6 @@ struct commit_list *reduce_heads(struct commit_list *heads);\n \n extern int commit_tree(const char *msg, unsigned char *tree,\n \t\tstruct commit_list *parents, unsigned char *ret,\n-\t\tconst char *author);\n+\t\t       const char *author, const char *sign_commit);\n \n #endif /* COMMIT_H */\ndiff --git a/notes-cache.c b/notes-cache.c\nindex 4c8984e..c36a960 100644\n--- a/notes-cache.c\n+++ b/notes-cache.c\n@@ -56,7 +56,7 @@ int notes_cache_write(struct notes_cache *c)\n \n \tif (write_notes_tree(&c->tree, tree_sha1))\n \t\treturn -1;\n-\tif (commit_tree(c->validity, tree_sha1, NULL, commit_sha1, NULL) < 0)\n+\tif (commit_tree(c->validity, tree_sha1, NULL, commit_sha1, NULL, NULL) < 0)\n \t\treturn -1;\n \tif (update_ref(\"update notes cache\", c->tree.ref, commit_sha1, NULL,\n \t\t       0, QUIET_ON_ERR) < 0)\ndiff --git a/notes-merge.c b/notes-merge.c\nindex e1aaf43..c29c434 100644\n--- a/notes-merge.c\n+++ b/notes-merge.c\n@@ -546,7 +546,7 @@ void create_notes_commit(struct notes_tree *t, struct commit_list *parents,\n \t\t/* else: t->ref points to nothing, assume root/orphan commit */\n \t}\n \n-\tif (commit_tree(msg, tree_sha1, parents, result_sha1, NULL))\n+\tif (commit_tree(msg, tree_sha1, parents, result_sha1, NULL, NULL))\n \t\tdie(\"Failed to commit notes tree to database\");\n }\n \n-- \n1.7.7.138.g7f41b6\n"},{"id":"177037","messageId":"CAJo=hJvWbjEM9E5AjPHgmQ=eY8xf=Q=xtukeu2Ur7auUqeabDg@mail.gmail.com","threadId":"28598","inReplyTo":"7vaa9f3pk8.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] commit: teach --gpg-sign option","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2011-10-06T15:50:22Z","receivedAt":"2011-10-06T15:50:22Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"On Wed, Oct 5, 2011 at 17:56, Junio C Hamano <gitster@pobox.com> wrote:\n> And this uses the gpg-interface.[ch] to allow signing the commit, i.e.\n>\n>    $ git commit --gpg-sign -m foo\n>    You need a passphrase to unlock the secret key for\n>    user: \"Junio C Hamano <gitster@pobox.com>\"\n>    4096-bit RSA key, ID 96AFE6CB, created 2011-10-03 (main key ID 713660A7)\n>\n>    [master 8457d13] foo\n>     1 files changed, 1 insertions(+), 0 deletions(-)\n>\n> The lines of GPG detached signature are placed in new header lines, after\n> the standard tree/parent/author/committer headers, instead of tucking the\n> signature block at the end of the commit log message text (similar to how\n> signed tag is done), for multiple reasons:\n\nI like this approach better than the prior \"push certificate\" idea.\nThe signature information is part of the history graph, and won't be\nstripped or forgotten to be published as commits flow through\ndevelopment trees, provided that the commit SHA-1 was preserved by not\ndoing a rewrite. There is no merge race on the server side to get a\nbranch updated.\n\nThe signature is automatically stripped by older Git tools like commit\n--amend, format-patch or rebase, where the signature would be made\ninvalid anyway by changing the tree, parent or committer. That is a\nnice bit of backward compatibility there. Of course there is some\nconcern this signature data will show up incorrectly on clients that\nare so ancient they don't understand the \"encoding\" header, but those\nclients are already very ancient, and already do not process encoding\ncorrectly.\n\n-- \nShawn.\n"},{"id":"177060","messageId":"20111006171107.GA10973@elie","threadId":"28598","inReplyTo":"CAJo=hJvWbjEM9E5AjPHgmQ=eY8xf=Q=xtukeu2Ur7auUqeabDg@mail.gmail.com","subject":"Re: [PATCH] commit: teach --gpg-sign option","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2011-10-06T17:11:07Z","receivedAt":"2011-10-06T17:11:07Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Shawn Pearce wrote:\n> On Wed, Oct 5, 2011 at 17:56, Junio C Hamano <gitster@pobox.com> wrote:\n\n>> And this uses the gpg-interface.[ch] to allow signing the commit, i.e.\n>>\n>>    $ git commit --gpg-sign -m foo\n>>    You need a passphrase to unlock the secret key for\n>>    user: \"Junio C Hamano <gitster@pobox.com>\"\n>>    4096-bit RSA key, ID 96AFE6CB, created 2011-10-03 (main key ID 713660A7)\n[...]\n> I like this approach better than the prior \"push certificate\" idea.\n> The signature information is part of the history graph\n\nI probably missed some earlier discussion (so please forgive me this),\nbut how is it intended to be used?  Would projects\n\n a. require as a matter of policy that all commits be signed\n b. just sign releases as usual, but as commits in the history graph\n    instead of tags\n c. sign the occasional especially interesting commit\n\nWhat happens if my old key is compromised and I want to throw away the\nsignatures and replace them with signatures using my new key?  How\ndoes this relate to the \"push certificate\" use case, which seemed to\nbe mostly about authenticating published branch tips with signatures\nthat are not necessarily important in the long term?\n\nIn other words, something like this feature sounds like a sensible way\nto commit the equivalent of a GPG-signed patch, but it doesn't seem\nlike a good fit for the \"push certificate\" use cases.\n"},{"id":"177065","messageId":"vpqfwj6xceo.fsf@bauges.imag.fr","threadId":"28598","inReplyTo":"20111006171107.GA10973@elie","subject":"Re: [PATCH] commit: teach --gpg-sign option","fromName":"Matthieu Moy","fromEmail":"matthieu.moy@grenoble-inp.fr","sentAt":"2011-10-06T17:22:55Z","receivedAt":"2011-10-06T17:22:55Z","isPatch":true,"sender":{"key":"matthieu.moy@grenoble-inp.fr","avatar":"https://gravatar.com/avatar/72c8a2705971a25dfaff23cece15130d405685845d911aedd5667ace277f3fc5?d=mp&s=160"},"body":"Jonathan Nieder <jrnieder@gmail.com> writes:\n\n> I probably missed some earlier discussion (so please forgive me this),\n\n(same here)\n\n> What happens if my old key is compromised and I want to throw away the\n> signatures and replace them with signatures using my new key?\n\nWith the patch we're discussing, signatures are part of history, hence\ncan't be modified after the fact without rewritting them.\n\n*But*, by design, unless sha1 itself is compromized (in which case Git\nwould need to change to another hash function, that would be no fun),\nsigning the tip of every branch is sufficient to sign the whole history.\n\nSo, your old signatures would remain there, and your new signature, for\nnew commits, would be added on top.\n\n> How does this relate to the \"push certificate\" use case, which seemed\n> to be mostly about authenticating published branch tips with\n> signatures that are not necessarily important in the long term?\n\nI'm wondering how this feature would fit in a typical flow, indeed.\nUsually, I hack for a while, and when I'm happy enough, I push. But I\ndon't take the decision of what to push at commit time, so if the idea\nis to sign only a few commits (i.e. the ones you push), then you should\ndecide this at commit time (\"hmm, I should commit --gpg-sign this time\nbecause I'm going to push this one\").\n\nIf the idea is to sign every commit, then there should be a config\noption so that we don't have to type it every time.\n\n-- \nMatthieu Moy\nhttp://www-verimag.imag.fr/~moy/\n"},{"id":"177090","messageId":"4E8DF6F6.3000706@drmicha.warpmail.net","threadId":"28598","inReplyTo":"vpqfwj6xceo.fsf@bauges.imag.fr","subject":"Re: [PATCH] commit: teach --gpg-sign option","fromName":"Michael J Gruber","fromEmail":"git@drmicha.warpmail.net","sentAt":"2011-10-06T18:44:06Z","receivedAt":"2011-10-06T18:44:06Z","isPatch":true,"sender":{"key":"git@grubix.eu","avatar":"https://avatars.githubusercontent.com/u/233215?v=4"},"body":"Matthieu Moy venit, vidit, dixit 06.10.2011 19:22:\n> Jonathan Nieder <jrnieder@gmail.com> writes:\n> \n>> I probably missed some earlier discussion (so please forgive me this),\n> \n> (same here)\n> \n>> What happens if my old key is compromised and I want to throw away the\n>> signatures and replace them with signatures using my new key?\n> \n> With the patch we're discussing, signatures are part of history, hence\n> can't be modified after the fact without rewritting them.\n> \n> *But*, by design, unless sha1 itself is compromized (in which case Git\n> would need to change to another hash function, that would be no fun),\n> signing the tip of every branch is sufficient to sign the whole history.\n> \n> So, your old signatures would remain there, and your new signature, for\n> new commits, would be added on top.\n> \n>> How does this relate to the \"push certificate\" use case, which seemed\n>> to be mostly about authenticating published branch tips with\n>> signatures that are not necessarily important in the long term?\n> \n> I'm wondering how this feature would fit in a typical flow, indeed.\n> Usually, I hack for a while, and when I'm happy enough, I push. But I\n> don't take the decision of what to push at commit time, so if the idea\n> is to sign only a few commits (i.e. the ones you push), then you should\n> decide this at commit time (\"hmm, I should commit --gpg-sign this time\n> because I'm going to push this one\").\n> \n> If the idea is to sign every commit, then there should be a config\n> option so that we don't have to type it every time.\n> \n\nSame concerns here. You can always\n\ngit commit --amend --gpg-sign\n\nthe commit at the tip, of course, and can even set things up to have\npush do this or remind you thereof.\n\nI really liked the signatures as notes (because I feel those signatures\nare attachments after the fact, not part of the commit) but can see how\ndistributing and merging them is non-trivial, and similarly checking\nthem at a point in time when they are not in a notes tree yet.\n\nMichael\n"},{"id":"177114","messageId":"7v8voxzu5f.fsf@alter.siamese.dyndns.org","threadId":"28598","inReplyTo":"20111006171107.GA10973@elie","subject":"Re: [PATCH] commit: teach --gpg-sign option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-10-06T21:29:00Z","receivedAt":"2011-10-06T21:29:00Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Nieder <jrnieder@gmail.com> writes:\n\n>> I like this approach better than the prior \"push certificate\" idea.\n>> The signature information is part of the history graph\n>\n> I probably missed some earlier discussion (so please forgive me this),\n\nHeh, you are not forgiven when the original message has a clear pointer to\nthe previous discussion ;-).\n\n> but how is it intended to be used?  Would projects\n>\n>  a. require as a matter of policy that all commits be signed\n\nPossible. Personally I would _not_ advise it but they can send in a patch\nto add a configuration or two if they do want to run their project that\nway.\n\n>  b. just sign releases as usual, but as commits in the history graph\n>     instead of tags\n\nThis is not meant to replace tags that is attached after the fact. If\nanything...\n\n>  c. sign the occasional especially interesting commit\n\n...with the definition of \"interesting\" being \"this is tonight's tip of\nbranch Linus is pushing out between releases\", \"I shortly will ask Linus\nto pull from the history leading up to this commit\", etc., this is the\nprimary scenario I personally envision the feature would be used in.\nWithout having to have \"nightly\" signed tags that clutter the tag\nnamespace, we can gain more confidence in the integrity of the history\nbetween officially tagged release points that may be a few months apart,\ndepending on projects.\n\n> ... How\n> does this relate to the \"push certificate\" use case, which seemed to\n> be mostly about authenticating published branch tips with signatures\n> that are not necessarily important in the long term?\n\nTo the upstream project whose participants are signing its history, these\npublish points may not be important in the longer term, but for downstream\nconsumers that have to fork from an in-between point for the next embedded\ndevice release track, it serves the same purpose as push certificates and\nis equally important: it allows them to limit the length of near-tip\nhistory that might have been tampered that needs to be validated. If the\ndownstream consumers fork only from a signed commit point, they only need\nto audit their own history without worrying about imported stuff after\nincident like what k.org had recently.\n\nI am also somewhat disturbed by \"have to sign when committing, long before\nI am confident that this is worth pushing\" aspect of this approach, but I\ndo not think it would be much of an issue in practice.\n\n - If you are only pubishing one independent branch, it is just the matter\n   of either \"commit --amend --gpg-sign\" or \"commit --allow-empty --gpg-sign\"\n   before you push;\n\n - If you are publishing multiple related branches (e.g. maint, master,\n   next) like I do, and want to correct a mistake discovered at a lower\n   branch (e.g. master) after it has been already merged in higher\n   branches (e.g. next), you have to either amend the tip of the lower\n   branch and rebuild the higher branches, or queue a fix-up to the tip of\n   the lower branch and merge the result to the higher branches _anyway_,\n   before you push.\n"},{"id":"177122","messageId":"robbat2-20111006T221637-481195848Z@orbis-terrarum.net","threadId":"28598","inReplyTo":"7vaa9f3pk8.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] commit: teach --gpg-sign option","fromName":"Robin H. Johnson","fromEmail":"robbat2@gentoo.org","sentAt":"2011-10-06T22:24:16Z","receivedAt":"2011-10-06T22:24:16Z","isPatch":true,"sender":{"key":"robbat2@gentoo.org","avatar":"https://avatars.githubusercontent.com/u/373898?v=4"},"body":"On Wed, Oct 05, 2011 at 05:56:55PM -0700,  Junio C Hamano wrote:\n> And this uses the gpg-interface.[ch] to allow signing the commit, i.e.\n> \n>     $ git commit --gpg-sign -m foo\n>     You need a passphrase to unlock the secret key for\n>     user: \"Junio C Hamano <gitster@pobox.com>\"\n>     4096-bit RSA key, ID 96AFE6CB, created 2011-10-03 (main key ID 713660A7)\n> \n>     [master 8457d13] foo\n>      1 files changed, 1 insertions(+), 0 deletions(-)\nI like it, but I have a couple of questions: \n1. Are the sig lines used in computed SHA1/commitid of a given commit (I\n   see examples w/ --amend and that would usually change the SHA1)?\n2. Can we allow more than one person sign a commit?\n3. If I have prepared a series on a local branch, and I want to sign all\n   of them, is this a variant of rebase or?\n\nI think this isn't a replacement for push certificates, but has value in\nitself. It's certainly provides better integration than the\nsignature-in-note variants.\n\n-- \nRobin Hugh Johnson\nGentoo Linux: Developer, Trustee & Infrastructure Lead\nE-Mail     : robbat2@gentoo.org\nGnuPG FP   : 11AC BA4F 4778 E3F6 E4ED  F38E B27B 944E 3488 4E85\n"},{"id":"177141","messageId":"4E8EBAFE.8020805@drmicha.warpmail.net","threadId":"28598","inReplyTo":"robbat2-20111006T221637-481195848Z@orbis-terrarum.net","subject":"Re: [PATCH] commit: teach --gpg-sign option","fromName":"Michael J Gruber","fromEmail":"git@drmicha.warpmail.net","sentAt":"2011-10-07T08:40:30Z","receivedAt":"2011-10-07T08:40:30Z","isPatch":true,"sender":{"key":"git@grubix.eu","avatar":"https://avatars.githubusercontent.com/u/233215?v=4"},"body":"[readding JCH to cc whom you dropped]\nRobin H. Johnson venit, vidit, dixit 07.10.2011 00:24:\n> On Wed, Oct 05, 2011 at 05:56:55PM -0700,  Junio C Hamano wrote:\n>> And this uses the gpg-interface.[ch] to allow signing the commit, i.e.\n>>\n>>     $ git commit --gpg-sign -m foo\n>>     You need a passphrase to unlock the secret key for\n>>     user: \"Junio C Hamano <gitster@pobox.com>\"\n>>     4096-bit RSA key, ID 96AFE6CB, created 2011-10-03 (main key ID 713660A7)\n>>\n>>     [master 8457d13] foo\n>>      1 files changed, 1 insertions(+), 0 deletions(-)\n> I like it, but I have a couple of questions: \n> 1. Are the sig lines used in computed SHA1/commitid of a given commit (I\n>    see examples w/ --amend and that would usually change the SHA1)?\n\nYes, just like with tag objects.\n\n> 2. Can we allow more than one person sign a commit?\n\nI don't think we support it now (tags) but we could allow concatenating\nsignatures since they are detached.\n\nThere's a somewhat delicate issue here: The signature (tag/commit) is a\nsignature on the contents of the object, and is itself not part of the\ncontents (or else we would have a chicken-egg-problem).\n\nThe sha1 of the object is determined by the content+header, i.e.\nincluding the signature.\n\nSo, by adding a signature, you change the sha1, but any existing\nsignature remains valid.\n\nThis is also how you can try to achieve a specific sha1 for a given\nobject content...\n\n> 3. If I have prepared a series on a local branch, and I want to sign all\n>    of them, is this a variant of rebase or?\n\nIf you really want to sign all you can rebase-i and use \"exec\" to do\nthat automatically, but there's no point: signing the top-most commit\nserves the same purpose.\n\n> I think this isn't a replacement for push certificates, but has value in\n> itself. It's certainly provides better integration than the\n> signature-in-note variants.\n> \n\nI do think it's meant as an implementation of push certificates. I don't\nsee any other value in it which could not be achieved by signed tags.\nCan you describe any?\n\nMichael\n"},{"id":"177150","messageId":"CACsJy8CJ-c7bL37L64-Z_s=75Bgfj4cMhmB4J2s=dH=v_mGYhw@mail.gmail.com","threadId":"28598","inReplyTo":"4E8EBAFE.8020805@drmicha.warpmail.net","subject":"Re: [PATCH] commit: teach --gpg-sign option","fromName":"Nguyen Thai Ngoc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2011-10-07T11:18:43Z","receivedAt":"2011-10-07T11:18:43Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"On Fri, Oct 7, 2011 at 7:40 PM, Michael J Gruber\n<git@drmicha.warpmail.net> wrote:\n>> 3. If I have prepared a series on a local branch, and I want to sign all\n>>    of them, is this a variant of rebase or?\n>\n> If you really want to sign all you can rebase-i and use \"exec\" to do\n> that automatically, but there's no point: signing the top-most commit\n> serves the same purpose.\n\nI think Gentoo wants identity, who (typically a Gentoo dev) signs this\nparticular commit and takes responsibility for making sure the commit\nfollows all kinds of Gentoo requirements. Commits can be passed\naround, author and committer are not a reliable source.\n-- \nDuy\n"},{"id":"177278","messageId":"4E91CCB0.4080809@drmicha.warpmail.net","threadId":"28598","inReplyTo":"4E8EBAFE.8020805@drmicha.warpmail.net","subject":"Re: [PATCH] commit: teach --gpg-sign option","fromName":"Michael J Gruber","fromEmail":"git@drmicha.warpmail.net","sentAt":"2011-10-09T16:32:48Z","receivedAt":"2011-10-09T16:32:48Z","isPatch":true,"sender":{"key":"git@grubix.eu","avatar":"https://avatars.githubusercontent.com/u/233215?v=4"},"body":"Michael J Gruber venit, vidit, dixit 07.10.2011 10:40:\n> [readding JCH to cc whom you dropped]\n> Robin H. Johnson venit, vidit, dixit 07.10.2011 00:24:\n>> On Wed, Oct 05, 2011 at 05:56:55PM -0700,  Junio C Hamano wrote:\n>>> And this uses the gpg-interface.[ch] to allow signing the commit, i.e.\n>>>\n>>>     $ git commit --gpg-sign -m foo\n>>>     You need a passphrase to unlock the secret key for\n>>>     user: \"Junio C Hamano <gitster@pobox.com>\"\n>>>     4096-bit RSA key, ID 96AFE6CB, created 2011-10-03 (main key ID 713660A7)\n>>>\n>>>     [master 8457d13] foo\n>>>      1 files changed, 1 insertions(+), 0 deletions(-)\n>> I like it, but I have a couple of questions: \n>> 1. Are the sig lines used in computed SHA1/commitid of a given commit (I\n>>    see examples w/ --amend and that would usually change the SHA1)?\n> \n> Yes, just like with tag objects.\n> \n>> 2. Can we allow more than one person sign a commit?\n> \n> I don't think we support it now (tags) but we could allow concatenating\n> signatures since they are detached.\n\nQuick update:\nSticking two signatures into a signed tag works perfectly with current\ngit, both signatures are verified and displayed.\n\nSo, it might make sense to have \"commit --amend\" append to an existing\nsignature.\n\n> There's a somewhat delicate issue here: The signature (tag/commit) is a\n> signature on the contents of the object, and is itself not part of the\n> contents (or else we would have a chicken-egg-problem).\n> \n> The sha1 of the object is determined by the content+header, i.e.\n> including the signature.\n\nNB: \"header\" is the wrong term here, it's \"data\" I think.\n\n> So, by adding a signature, you change the sha1, but any existing\n> signature remains valid.\n> \n> This is also how you can try to achieve a specific sha1 for a given\n> object content...\n> \n"},{"id":"177282","messageId":"4E91FD57.7050808@drmicha.warpmail.net","threadId":"28598","inReplyTo":"7vaa9f3pk8.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] commit: teach --gpg-sign option","fromName":"Michael J Gruber","fromEmail":"git@drmicha.warpmail.net","sentAt":"2011-10-09T20:00:23Z","receivedAt":"2011-10-09T20:00:23Z","isPatch":true,"sender":{"key":"git@grubix.eu","avatar":"https://avatars.githubusercontent.com/u/233215?v=4"},"body":"Junio C Hamano venit, vidit, dixit 06.10.2011 02:56:\n> And this uses the gpg-interface.[ch] to allow signing the commit, i.e.\n> \n>     $ git commit --gpg-sign -m foo\n>     You need a passphrase to unlock the secret key for\n>     user: \"Junio C Hamano <gitster@pobox.com>\"\n>     4096-bit RSA key, ID 96AFE6CB, created 2011-10-03 (main key ID 713660A7)\n> \n>     [master 8457d13] foo\n>      1 files changed, 1 insertions(+), 0 deletions(-)\n> \n> The lines of GPG detached signature are placed in new header lines, after\n> the standard tree/parent/author/committer headers, instead of tucking the\n> signature block at the end of the commit log message text (similar to how\n> signed tag is done), for multiple reasons:\n> \n>  - The signature won't clutter output from \"git log\" and friends if it is\n>    in the extra header. If we place it at the end of the log message, we\n>    would need to teach \"git log\" and friends to strip the signature block\n>    with an option.\n> \n>  - Teaching new versions of \"git log\" and \"gitk\" to optionally verify and\n>    show signatures is cleaner if we structurally know where the signature\n>    block is (instead of scanning in the commit log message).\n> \n>  - The signature needs to be stripped upon various commit rewriting\n>    operations, e.g. rebase, filter-branch, etc. They all already ignore\n>    unknown headers, but if we place signature in the log message, all of\n>    these tools (and third-party tools) also need to learn how a signature\n>    block would look like.\n> \n>  - When we added the optional encoding header, all the tools (both in tree\n>    and third-party) that acts on the raw commit object should have been\n>    fixed to ignore headers they do not understand, so it is not like that\n>    new header would be more likely to break than extra text in the commit.\n> \n> A commit made with the above sample sequence would look like this:\n> \n>     $ git cat-file commit HEAD\n>     tree 3cd71d90e3db4136e5260ab54599791c4f883b9d\n>     parent b87755351a47b09cb27d6913e6e0e17e6254a4d4\n>     author Junio C Hamano <gitster@pobox.com> 1317862251 -0700\n>     committer Junio C Hamano <gitster@pobox.com> 1317862251 -0700\n>     sig -----BEGIN PGP SIGNATURE-----\n>     sig Version: GnuPG v1.4.10 (GNU/Linux)\n>     sig\n>     sig iQIcBAABAgAGBQJOjPtrAAoJELC16IaWr+bL4TMP/RSe2Y/jYnCkds9unO5JEnfG\n>     sig ...\n>     sig =dt98\n>     sig -----END PGP SIGNATURE-----\n> \n>     foo\n> \n\nI just noticed that this format differs from the one of signed tags.\nWhat special reason is there for the \"sig \" indentation?\n\nBTW: commit --amend --gpg-sign strips an existing signature rather than\nadding one. We might want the user to have a say here.\n\n> but \"git log\" (unless you ask for it with --pretty=raw) output is not\n> cluttered with the signature information.\n> \n> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n> ---\n> \n> Cf.\n>     Message-ID: <7vfwjgui8s.fsf_-_@alter.siamese.dyndns.org>\n>     http://thread.gmane.org/gmane.comp.version-control.git/182297/focus=182384\n> \n>  builtin/commit-tree.c |   24 +++++++++++++++++++++---\n>  builtin/commit.c      |   12 ++++++++++--\n>  builtin/merge.c       |   16 ++++++++++++++--\n>  commit.c              |   41 ++++++++++++++++++++++++++++++++++++++++-\n>  commit.h              |    2 +-\n>  notes-cache.c         |    2 +-\n>  notes-merge.c         |    2 +-\n>  7 files changed, 88 insertions(+), 11 deletions(-)\n> \n> diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c\n> index d083795..a17811f 100644\n> --- a/builtin/commit-tree.c\n> +++ b/builtin/commit-tree.c\n> @@ -8,8 +8,9 @@\n>  #include \"tree.h\"\n>  #include \"builtin.h\"\n>  #include \"utf8.h\"\n> +#include \"gpg-interface.h\"\n>  \n> -static const char commit_tree_usage[] = \"git commit-tree <sha1> [(-p <sha1>)...] < changelog\";\n> +static const char commit_tree_usage[] = \"git commit-tree [-S<signer>] <sha1> [(-p <sha1>)...] < changelog\";\n>  \n>  static void new_parent(struct commit *parent, struct commit_list **parents_p)\n>  {\n> @@ -25,6 +26,14 @@ static void new_parent(struct commit *parent, struct commit_list **parents_p)\n>  \tcommit_list_insert(parent, parents_p);\n>  }\n>  \n> +static int commit_tree_config(const char *var, const char *value, void *cb)\n> +{\n> +\tint status = git_gpg_config(var, value, NULL);\n> +\tif (status)\n> +\t\treturn status;\n> +\treturn git_default_config(var, value, cb);\n> +}\n> +\n>  int cmd_commit_tree(int argc, const char **argv, const char *prefix)\n>  {\n>  \tint i;\n> @@ -32,11 +41,19 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)\n>  \tunsigned char tree_sha1[20];\n>  \tunsigned char commit_sha1[20];\n>  \tstruct strbuf buffer = STRBUF_INIT;\n> +\tconst char *sign_commit = NULL;\n>  \n> -\tgit_config(git_default_config, NULL);\n> +\tgit_config(commit_tree_config, NULL);\n>  \n>  \tif (argc < 2 || !strcmp(argv[1], \"-h\"))\n>  \t\tusage(commit_tree_usage);\n> +\n> +\tif (!memcmp(argv[1], \"-S\", 2)) {\n> +\t\tsign_commit = argv[1] + 2;\n> +\t\targv++;\n> +\t\targc--;\n> +\t}\n> +\n>  \tif (get_sha1(argv[1], tree_sha1))\n>  \t\tdie(\"Not a valid object name %s\", argv[1]);\n>  \n> @@ -56,7 +73,8 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)\n>  \tif (strbuf_read(&buffer, 0, 0) < 0)\n>  \t\tdie_errno(\"git commit-tree: failed to read\");\n>  \n> -\tif (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) {\n> +\tif (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1,\n> +\t\t\tNULL, sign_commit)) {\n>  \t\tstrbuf_release(&buffer);\n>  \t\treturn 1;\n>  \t}\n> diff --git a/builtin/commit.c b/builtin/commit.c\n> index cbc9613..90cf7e8 100644\n> --- a/builtin/commit.c\n> +++ b/builtin/commit.c\n> @@ -26,6 +26,7 @@\n>  #include \"unpack-trees.h\"\n>  #include \"quote.h\"\n>  #include \"submodule.h\"\n> +#include \"gpg-interface.h\"\n>  \n>  static const char * const builtin_commit_usage[] = {\n>  \t\"git commit [options] [--] <filepattern>...\",\n> @@ -85,6 +86,8 @@ static int all, edit_flag, also, interactive, patch_interactive, only, amend, si\n>  static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;\n>  static int no_post_rewrite, allow_empty_message;\n>  static char *untracked_files_arg, *force_date, *ignore_submodule_arg;\n> +static char *sign_commit;\n> +\n>  /*\n>   * The default commit message cleanup mode will remove the lines\n>   * beginning with # (shell comments) and leading and trailing\n> @@ -144,6 +147,8 @@ static struct option builtin_commit_options[] = {\n>  \tOPT_BOOLEAN('e', \"edit\", &edit_flag, \"force edit of commit\"),\n>  \tOPT_STRING(0, \"cleanup\", &cleanup_arg, \"default\", \"how to strip spaces and #comments from message\"),\n>  \tOPT_BOOLEAN(0, \"status\", &include_status, \"include status in commit message template\"),\n> +\t{ OPTION_STRING, 'S', \"gpg-sign\", &sign_commit, \"key id\",\n> +\t  \"GPG sign commit\", PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n>  \t/* end commit message options */\n>  \n>  \tOPT_GROUP(\"Commit contents options\"),\n> @@ -1323,6 +1328,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1,\n>  static int git_commit_config(const char *k, const char *v, void *cb)\n>  {\n>  \tstruct wt_status *s = cb;\n> +\tint status;\n>  \n>  \tif (!strcmp(k, \"commit.template\"))\n>  \t\treturn git_config_pathname(&template_file, k, v);\n> @@ -1330,7 +1336,9 @@ static int git_commit_config(const char *k, const char *v, void *cb)\n>  \t\tinclude_status = git_config_bool(k, v);\n>  \t\treturn 0;\n>  \t}\n> -\n> +\tstatus = git_gpg_config(k, v, NULL);\n> +\tif (status)\n> +\t\treturn status;\n>  \treturn git_status_config(k, v, s);\n>  }\n>  \n> @@ -1481,7 +1489,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n>  \t}\n>  \n>  \tif (commit_tree(sb.buf, active_cache_tree->sha1, parents, sha1,\n> -\t\t\tauthor_ident.buf)) {\n> +\t\t\tauthor_ident.buf, sign_commit)) {\n>  \t\trollback_index_files();\n>  \t\tdie(_(\"failed to write commit object\"));\n>  \t}\n> diff --git a/builtin/merge.c b/builtin/merge.c\n> index ab4077f..53cff02 100644\n> --- a/builtin/merge.c\n> +++ b/builtin/merge.c\n> @@ -26,6 +26,7 @@\n>  #include \"merge-recursive.h\"\n>  #include \"resolve-undo.h\"\n>  #include \"remote.h\"\n> +#include \"gpg-interface.h\"\n>  \n>  #define DEFAULT_TWOHEAD (1<<0)\n>  #define DEFAULT_OCTOPUS (1<<1)\n> @@ -63,6 +64,7 @@ static int allow_rerere_auto;\n>  static int abort_current_merge;\n>  static int show_progress = -1;\n>  static int default_to_upstream;\n> +static const char *sign_commit;\n>  \n>  static struct strategy all_strategy[] = {\n>  \t{ \"recursive\",  DEFAULT_TWOHEAD | NO_TRIVIAL },\n> @@ -206,6 +208,8 @@ static struct option builtin_merge_options[] = {\n>  \tOPT_BOOLEAN(0, \"abort\", &abort_current_merge,\n>  \t\t\"abort the current in-progress merge\"),\n>  \tOPT_SET_INT(0, \"progress\", &show_progress, \"force progress reporting\", 1),\n> +\t{ OPTION_STRING, 'S', \"gpg-sign\", &sign_commit, \"key id\",\n> +\t  \"GPG sign commit\", PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n>  \tOPT_END()\n>  };\n>  \n> @@ -525,6 +529,8 @@ static void parse_branch_merge_options(char *bmo)\n>  \n>  static int git_merge_config(const char *k, const char *v, void *cb)\n>  {\n> +\tint status;\n> +\n>  \tif (branch && !prefixcmp(k, \"branch.\") &&\n>  \t\t!prefixcmp(k + 7, branch) &&\n>  \t\t!strcmp(k + 7 + strlen(branch), \".mergeoptions\")) {\n> @@ -562,6 +568,10 @@ static int git_merge_config(const char *k, const char *v, void *cb)\n>  \t\tdefault_to_upstream = git_config_bool(k, v);\n>  \t\treturn 0;\n>  \t}\n> +\n> +\tstatus = git_gpg_config(k, v, NULL);\n> +\tif (status)\n> +\t\treturn status;\n>  \treturn git_diff_ui_config(k, v, cb);\n>  }\n>  \n> @@ -870,7 +880,8 @@ static int merge_trivial(void)\n>  \tparent->next->item = remoteheads->item;\n>  \tparent->next->next = NULL;\n>  \trun_prepare_commit_msg();\n> -\tcommit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);\n> +\tcommit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL,\n> +\t\t    sign_commit);\n>  \tfinish(result_commit, \"In-index merge\");\n>  \tdrop_save();\n>  \treturn 0;\n> @@ -900,7 +911,8 @@ static int finish_automerge(struct commit_list *common,\n>  \tfree_commit_list(remoteheads);\n>  \tstrbuf_addch(&merge_msg, '\\n');\n>  \trun_prepare_commit_msg();\n> -\tcommit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);\n> +\tcommit_tree(merge_msg.buf, result_tree, parents, result_commit,\n> +\t\t    NULL, sign_commit);\n>  \tstrbuf_addf(&buf, \"Merge made by the '%s' strategy.\", wt_strategy);\n>  \tfinish(result_commit, buf.buf);\n>  \tstrbuf_release(&buf);\n> diff --git a/commit.c b/commit.c\n> index 97b4327..969435d 100644\n> --- a/commit.c\n> +++ b/commit.c\n> @@ -6,6 +6,7 @@\n>  #include \"diff.h\"\n>  #include \"revision.h\"\n>  #include \"notes.h\"\n> +#include \"gpg-interface.h\"\n>  \n>  int save_commit_buffer = 1;\n>  \n> @@ -814,6 +815,41 @@ struct commit_list *reduce_heads(struct commit_list *heads)\n>  \treturn result;\n>  }\n>  \n> +static int do_sign_commit(struct strbuf *buf, const char *keyid)\n> +{\n> +\tstruct strbuf sig = STRBUF_INIT;\n> +\tint inspos, copypos;\n> +\tconst char gpg_sig[] = \"sig \";\n> +\tconst int header_len = sizeof(gpg_sig) - 1;\n> +\n> +\t/* find the end of the header */\n> +\tinspos = strstr(buf->buf, \"\\n\\n\") - buf->buf + 1;\n> +\tcopypos = buf->len;\n> +\n> +\tstrbuf_addbuf(&sig, buf);\n> +\n> +\tif (!keyid || !*keyid)\n> +\t\tkeyid = get_signing_key();\n> +\tif (sign_buffer(&sig, keyid)) {\n> +\t\tstrbuf_release(&sig);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\twhile (sig.buf[copypos]) {\n> +\t\tconst char *bol = sig.buf + copypos;\n> +\t\tconst char *eol = strchrnul(bol, '\\n');\n> +\t\tint len = (eol - bol) + !!*eol;\n> +\t\tstrbuf_insert(buf, inspos, gpg_sig, header_len);\n> +\t\tinspos += header_len;\n> +\t\tstrbuf_insert(buf, inspos, bol, len);\n> +\t\tinspos += len;\n> +\t\tcopypos += len;\n> +\t}\n> +\tstrbuf_release(&sig);\n> +\treturn 0;\n> +}\n> +\n> +\n>  static const char commit_utf8_warn[] =\n>  \"Warning: commit message does not conform to UTF-8.\\n\"\n>  \"You may want to amend it after fixing the message, or set the config\\n\"\n> @@ -821,7 +857,7 @@ static const char commit_utf8_warn[] =\n>  \n>  int commit_tree(const char *msg, unsigned char *tree,\n>  \t\tstruct commit_list *parents, unsigned char *ret,\n> -\t\tconst char *author)\n> +\t\tconst char *author, const char *sign_commit)\n>  {\n>  \tint result;\n>  \tint encoding_is_utf8;\n> @@ -864,6 +900,9 @@ int commit_tree(const char *msg, unsigned char *tree,\n>  \tif (encoding_is_utf8 && !is_utf8(buffer.buf))\n>  \t\tfprintf(stderr, commit_utf8_warn);\n>  \n> +\tif (sign_commit && do_sign_commit(&buffer, sign_commit))\n> +\t\treturn -1;\n> +\n>  \tresult = write_sha1_file(buffer.buf, buffer.len, commit_type, ret);\n>  \tstrbuf_release(&buffer);\n>  \treturn result;\n> diff --git a/commit.h b/commit.h\n> index 12d100b8..8c2419b 100644\n> --- a/commit.h\n> +++ b/commit.h\n> @@ -175,6 +175,6 @@ struct commit_list *reduce_heads(struct commit_list *heads);\n>  \n>  extern int commit_tree(const char *msg, unsigned char *tree,\n>  \t\tstruct commit_list *parents, unsigned char *ret,\n> -\t\tconst char *author);\n> +\t\t       const char *author, const char *sign_commit);\n>  \n>  #endif /* COMMIT_H */\n> diff --git a/notes-cache.c b/notes-cache.c\n> index 4c8984e..c36a960 100644\n> --- a/notes-cache.c\n> +++ b/notes-cache.c\n> @@ -56,7 +56,7 @@ int notes_cache_write(struct notes_cache *c)\n>  \n>  \tif (write_notes_tree(&c->tree, tree_sha1))\n>  \t\treturn -1;\n> -\tif (commit_tree(c->validity, tree_sha1, NULL, commit_sha1, NULL) < 0)\n> +\tif (commit_tree(c->validity, tree_sha1, NULL, commit_sha1, NULL, NULL) < 0)\n>  \t\treturn -1;\n>  \tif (update_ref(\"update notes cache\", c->tree.ref, commit_sha1, NULL,\n>  \t\t       0, QUIET_ON_ERR) < 0)\n> diff --git a/notes-merge.c b/notes-merge.c\n> index e1aaf43..c29c434 100644\n> --- a/notes-merge.c\n> +++ b/notes-merge.c\n> @@ -546,7 +546,7 @@ void create_notes_commit(struct notes_tree *t, struct commit_list *parents,\n>  \t\t/* else: t->ref points to nothing, assume root/orphan commit */\n>  \t}\n>  \n> -\tif (commit_tree(msg, tree_sha1, parents, result_sha1, NULL))\n> +\tif (commit_tree(msg, tree_sha1, parents, result_sha1, NULL, NULL))\n>  \t\tdie(\"Failed to commit notes tree to database\");\n>  }\n>  \n"},{"id":"177284","messageId":"7v8votrhbr.fsf@alter.siamese.dyndns.org","threadId":"28598","inReplyTo":"4E91FD57.7050808@drmicha.warpmail.net","subject":"Re: [PATCH] commit: teach --gpg-sign option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-10-09T21:22:16Z","receivedAt":"2011-10-09T21:22:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Michael J Gruber <git@drmicha.warpmail.net> writes:\n\n> I just noticed that this format differs from the one of signed tags.\n> What special reason is there for the \"sig \" indentation?\n\nRead the part of the message you are quoting.\n"},{"id":"177287","messageId":"7v4nzhrebp.fsf@alter.siamese.dyndns.org","threadId":"28598","inReplyTo":"4E91FD57.7050808@drmicha.warpmail.net","subject":"Re: [PATCH] commit: teach --gpg-sign option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-10-09T22:27:06Z","receivedAt":"2011-10-09T22:27:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Michael J Gruber <git@drmicha.warpmail.net> writes:\n\n> BTW: commit --amend --gpg-sign strips an existing signature rather than\n> adding one. We might want the user to have a say here.\n\nI think it deserves a separate command (commit --add-gpg-sign) that is\nused _only_ to add an additional signature by another person without\naffecting anything else in the commit (i.e. the tree, the parents and the\nauthor and committership information) from the viewpoint of the workflow,\n\nObviously that \"add-signature\" mode needs to be aware of the existing\nsignature. It is a deliberate design decision to strip existing signature\nwhen anything in the commit changes, which is the norm for --amend.\n"},{"id":"177288","messageId":"robbat2-20111009T225253-591026811Z@orbis-terrarum.net","threadId":"28598","inReplyTo":"4E8EBAFE.8020805@drmicha.warpmail.net","subject":"Re: [PATCH] commit: teach --gpg-sign option","fromName":"Robin H. Johnson","fromEmail":"robbat2@gentoo.org","sentAt":"2011-10-09T22:57:28Z","receivedAt":"2011-10-09T22:57:28Z","isPatch":true,"sender":{"key":"robbat2@gentoo.org","avatar":"https://avatars.githubusercontent.com/u/373898?v=4"},"body":"On Fri, Oct 07, 2011 at 10:40:30AM +0200,  Michael J Gruber wrote:\n> [readding JCH to cc whom you dropped]\n> Robin H. Johnson venit, vidit, dixit 07.10.2011 00:24:\n> > On Wed, Oct 05, 2011 at 05:56:55PM -0700,  Junio C Hamano wrote:\n> >> And this uses the gpg-interface.[ch] to allow signing the commit, i.e.\n> >>\n> >>     $ git commit --gpg-sign -m foo\n> >>     You need a passphrase to unlock the secret key for\n> >>     user: \"Junio C Hamano <gitster@pobox.com>\"\n> >>     4096-bit RSA key, ID 96AFE6CB, created 2011-10-03 (main key ID 713660A7)\n> >>\n> >>     [master 8457d13] foo\n> >>      1 files changed, 1 insertions(+), 0 deletions(-)\n> > I like it, but I have a couple of questions: \n> > 1. Are the sig lines used in computed SHA1/commitid of a given commit (I\n> >    see examples w/ --amend and that would usually change the SHA1)?\n> Yes, just like with tag objects.\nOk, at the core, this is going to pose a problem with multiple\nsignatures.\n\nWorkflow example:\n1. Dev1 creates a commit, signs it, pushes to central repo.\n2. Dev2 pulls, signs the tip commit, pushes it back.\n\nSince signing model here actually alters the commit, the push by Dev2\nloses the history point of a commit with only a single signature, like\nif somebody pushes a rewritten history (which should usually be\nprohibited).\n\nThe push certificate variant of signing does permit this case without\nbreaking history.\n\n> > I think this isn't a replacement for push certificates, but has value in\n> > itself. It's certainly provides better integration than the\n> > signature-in-note variants.\n> > \n> \n> I do think it's meant as an implementation of push certificates. I don't\n> see any other value in it which could not be achieved by signed tags.\n> Can you describe any?\nIdentify of the committer for verification.\n\n-- \nRobin Hugh Johnson\nGentoo Linux: Developer, Trustee & Infrastructure Lead\nE-Mail     : robbat2@gentoo.org\nGnuPG FP   : 11AC BA4F 4778 E3F6 E4ED  F38E B27B 944E 3488 4E85\n"},{"id":"177289","messageId":"7vmxd9pxd2.fsf@alter.siamese.dyndns.org","threadId":"28598","inReplyTo":"robbat2-20111009T225253-591026811Z@orbis-terrarum.net","subject":"Re: [PATCH] commit: teach --gpg-sign option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-10-09T23:18:49Z","receivedAt":"2011-10-09T23:18:49Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Robin H. Johnson\" <robbat2@gentoo.org> writes:\n\n> Workflow example:\n> 1. Dev1 creates a commit, signs it, pushes to central repo.\n> 2. Dev2 pulls, signs the tip commit, pushes it back.\n\nI personally am not sympathetic to such a \"sign every and all commits by\nmultiple people\" workflow. If you really want to do such a thing, you can\nhave the second and subsequent one to create a new commit on top whose\nsole purpose is to hold such a signature (commit --allow-empty --gpg-sig),\nor use signed tags.\n"},{"id":"177294","messageId":"7vwrcdo32l.fsf@alter.siamese.dyndns.org","threadId":"28598","inReplyTo":"CACBZZX6xsnAv4S8zAqi08bcqrghZ8nKdzFP=UNCqZOqrEeLFnA@mail.gmail.com","subject":"Re: [PATCH] commit: teach --gpg-sign option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-10-10T04:58:26Z","receivedAt":"2011-10-10T04:58:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> A nit: no other commit header is an abbreviation, it would be more\n> consistent to use \"signature\" instead of \"sig\".\n\nYou are probably right.\n"},{"id":"177300","messageId":"4E92919F.2030007@drmicha.warpmail.net","threadId":"28598","inReplyTo":"7v4nzhrebp.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] commit: teach --gpg-sign option","fromName":"Michael J Gruber","fromEmail":"git@drmicha.warpmail.net","sentAt":"2011-10-10T06:33:03Z","receivedAt":"2011-10-10T06:33:03Z","isPatch":true,"sender":{"key":"git@grubix.eu","avatar":"https://avatars.githubusercontent.com/u/233215?v=4"},"body":"Junio C Hamano venit, vidit, dixit 10.10.2011 00:27:\n> Michael J Gruber <git@drmicha.warpmail.net> writes:\n> \n>> BTW: commit --amend --gpg-sign strips an existing signature rather than\n>> adding one. We might want the user to have a say here.\n> \n> I think it deserves a separate command (commit --add-gpg-sign) that is\n> used _only_ to add an additional signature by another person without\n> affecting anything else in the commit (i.e. the tree, the parents and the\n> author and committership information) from the viewpoint of the workflow,\n\nAgreed, as I asked \"the user to have a say\".\n\n> Obviously that \"add-signature\" mode needs to be aware of the existing\n> signature. It is a deliberate design decision to strip existing signature\n> when anything in the commit changes, which is the norm for --amend.\n\nWhat norm? --amend keeps some header fields and discards others. In\nfact, signing a commit \"without changing it\" (i.e. keeping tree, parents\netc., alias \"--amend -C HEAD\") should be the normal use case for signing\nthe tip of an existing branch. I mean, I have no problems adding to this:\n\ngit help fixup\n`git fixup' is aliased to `commit --amend -C HEAD'\n\nBut what is the best default for the workflows that we encourage (commit\nearly, ...)? You answer a pull-request which happens to be a\nfast-forward, sign the tip and suddenly you've taken over ownership (and\nchanged dates)??? Signing a commit should not do this.\n\nMichael\n"},{"id":"177299","messageId":"4E9291B6.2090201@drmicha.warpmail.net","threadId":"28598","inReplyTo":"7v8votrhbr.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] commit: teach --gpg-sign option","fromName":"Michael J Gruber","fromEmail":"git@drmicha.warpmail.net","sentAt":"2011-10-10T06:33:26Z","receivedAt":"2011-10-10T06:33:26Z","isPatch":true,"sender":{"key":"git@grubix.eu","avatar":"https://avatars.githubusercontent.com/u/233215?v=4"},"body":"Junio C Hamano venit, vidit, dixit 09.10.2011 23:22:\n> Michael J Gruber <git@drmicha.warpmail.net> writes:\n> \n>> I just noticed that this format differs from the one of signed\n>> tags. What special reason is there for the \"sig \" indentation?\n> \n> Read the part of the message you are quoting.\n\nI certainly did, and certainly did not find any mention. Do you think I\nwould have asked otherwise? I'm trying to be helpful by testing out a\npatch in flight. That is: *I* am trying to be helpful.\n\nThis\n> The lines of GPG detached signature are placed in new header lines, after\n> the standard tree/parent/author/committer headers, instead of tucking the\n> signature block at the end of the commit log message text (similar to how\n> signed tag is done), for multiple reasons:\ngave me the impression that commit signatures are done \"similar to how\nsigned tag is done\".\n\n*After* doing several \"cat-file\" and after your insisting that you had\ndescribed the \"sig \" indent I come to the conclusion that you\nimplemented it this way \"instead of... [doing it] similar to how signed\ntag is done\".\n\nBefore that, I misread those paragraphs (togetheter with the\nnon-existing object format doc) to mean that have a section in the\nobject which is ignored automatically, which is where tag signatures are\n(while in fact they are not) and where commit signatures will go.\n\nI have to say I dislike the fact that we would have different signature\nformats. But I have spent too much time on this unnecessarily already.\n\nMichael\n"},{"id":"177319","messageId":"7vehykold0.fsf@alter.siamese.dyndns.org","threadId":"28598","inReplyTo":"4E9291B6.2090201@drmicha.warpmail.net","subject":"Re: [PATCH] commit: teach --gpg-sign option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-10-10T16:35:39Z","receivedAt":"2011-10-10T16:35:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Michael J Gruber <git@drmicha.warpmail.net> writes:\n\n> Junio C Hamano venit, vidit, dixit 09.10.2011 23:22:\n>> Michael J Gruber <git@drmicha.warpmail.net> writes:\n>> \n>>> I just noticed that this format differs from the one of signed\n>>> tags. What special reason is there for the \"sig \" indentation?\n>> \n>> Read the part of the message you are quoting.\n>\n> I certainly did, and certainly did not find any mention. Do you think I\n> would have asked otherwise? I'm trying to be helpful by testing out a\n> patch in flight. That is: *I* am trying to be helpful.\n\nSurely, sorry and thanks. I should have pointed out where I _thought_ I\nspelled out the reason but apparently in an ineffective way (as my wording\ndid not convey what I wanted to say at least to you) a bit more clearly.\n\n> This\n>> The lines of GPG detached signature are placed in new header lines, after\n>> the standard tree/parent/author/committer headers, instead of tucking the\n>> signature block at the end of the commit log message text (similar to how\n>> signed tag is done), for multiple reasons:\n> gave me the impression that commit signatures are done \"similar to how\n> signed tag is done\".\n\nYeah, sorry if that sentence parses badly.\n\nIt was trying to say:\n\n - the sig is in header lines\n   - which is different (instead of) from tucking it at the end of text\n     - by the way that \"tucking at the end\" would have been similar to\n       signed tag\n\nThe reasons why signatures in the header is better than tucking at the end\nare enumerated in the part you did not quote in this message but in the\npart you did quote in the previous message.\n"},{"id":"177320","messageId":"7vaa98okwl.fsf@alter.siamese.dyndns.org","threadId":"28598","inReplyTo":"4E92919F.2030007@drmicha.warpmail.net","subject":"Re: [PATCH] commit: teach --gpg-sign option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-10-10T16:45:30Z","receivedAt":"2011-10-10T16:45:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Michael J Gruber <git@drmicha.warpmail.net> writes:\n\n> What norm? --amend keeps some header fields and discards others. In\n> fact, signing a commit \"without changing it\" (i.e. keeping tree, parents\n> etc., alias \"--amend -C HEAD\") should be the normal use case for signing\n> the tip of an existing branch. I mean, I have no problems adding to this:\n>\n> git help fixup\n> `git fixup' is aliased to `commit --amend -C HEAD'\n\nYou are *additionally* saying \"-C HEAD\" in an non-standard alias. Isn't\nthat enough indication that a vanila \"--amend\" is intended to record the\ncommit based on the updated context in which the new commit is made?\nE.g. the authorship of the patch is still the same but committer\ninformation is updated.\n\n> But what is the best default for the workflows that we encourage (commit\n> early, ...)? You answer a pull-request which happens to be a\n> fast-forward, sign the tip and suddenly you've taken over ownership (and\n> changed dates)??? Signing a commit should not do this.\n\nI personally think a pull that is made in response to a pull-request,\ni.e. the upstream merging from lieutenant, especially when the\nauthenticity of the puller matters, is perfectly fine with --no-ff.\n\nUnlike the sign-less \"we together made these history and nobody really\nowns the result\" (aka \"Linus hates --no-ff merge because people do that to\nleave a mark by peeing in the snow, without adding anything of value in\nthe history\"), the whole purpose of signing a commit in the scenario you\nmentioned is for the puller to leave his mark in the history.\n"},{"id":"177351","messageId":"robbat2-20111011T003728-410722439Z@orbis-terrarum.net","threadId":"28598","inReplyTo":"7vmxd9pxd2.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] commit: teach --gpg-sign option","fromName":"Robin H. Johnson","fromEmail":"robbat2@gentoo.org","sentAt":"2011-10-11T00:38:53Z","receivedAt":"2011-10-11T00:38:53Z","isPatch":true,"sender":{"key":"robbat2@gentoo.org","avatar":"https://avatars.githubusercontent.com/u/373898?v=4"},"body":"On Sun, Oct 09, 2011 at 04:18:49PM -0700,  Junio C Hamano wrote:\n> \"Robin H. Johnson\" <robbat2@gentoo.org> writes:\n> > Workflow example:\n> > 1. Dev1 creates a commit, signs it, pushes to central repo.\n> > 2. Dev2 pulls, signs the tip commit, pushes it back.\n> \n> I personally am not sympathetic to such a \"sign every and all commits by\n> multiple people\" workflow. If you really want to do such a thing, you can\n> have the second and subsequent one to create a new commit on top whose\n> sole purpose is to hold such a signature (commit --allow-empty --gpg-sig),\n> or use signed tags.\nFor this case, I think having the push certificates works much better.\n\nNo easy solution to all of this, just lots of yak-shaving :-(.\n\n-- \nRobin Hugh Johnson\nGentoo Linux: Developer, Trustee & Infrastructure Lead\nE-Mail     : robbat2@gentoo.org\nGnuPG FP   : 11AC BA4F 4778 E3F6 E4ED  F38E B27B 944E 3488 4E85\n"},{"id":"177357","messageId":"4E93E4B4.9070901@drmicha.warpmail.net","threadId":"28598","inReplyTo":"7vaa98okwl.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] commit: teach --gpg-sign option","fromName":"Michael J Gruber","fromEmail":"git@drmicha.warpmail.net","sentAt":"2011-10-11T06:39:48Z","receivedAt":"2011-10-11T06:39:48Z","isPatch":true,"sender":{"key":"git@grubix.eu","avatar":"https://avatars.githubusercontent.com/u/233215?v=4"},"body":"Junio C Hamano venit, vidit, dixit 10.10.2011 18:45:\n> Michael J Gruber <git@drmicha.warpmail.net> writes:\n> \n>> What norm? --amend keeps some header fields and discards others. In\n>> fact, signing a commit \"without changing it\" (i.e. keeping tree, parents\n>> etc., alias \"--amend -C HEAD\") should be the normal use case for signing\n>> the tip of an existing branch. I mean, I have no problems adding to this:\n>>\n>> git help fixup\n>> `git fixup' is aliased to `commit --amend -C HEAD'\n> \n> You are *additionally* saying \"-C HEAD\" in an non-standard alias. Isn't\n> that enough indication that a vanila \"--amend\" is intended to record the\n> commit based on the updated context in which the new commit is made?\n> E.g. the authorship of the patch is still the same but committer\n> information is updated.\n\nI was more referring to leaving \"parent\" and \"tree\" headers in place,\nwhich is a bit of a screwed comparison (because it relies on an\nunchanged index).\n\n>> But what is the best default for the workflows that we encourage (commit\n>> early, ...)? You answer a pull-request which happens to be a\n>> fast-forward, sign the tip and suddenly you've taken over ownership (and\n>> changed dates)??? Signing a commit should not do this.\n> \n> I personally think a pull that is made in response to a pull-request,\n> i.e. the upstream merging from lieutenant, especially when the\n> authenticity of the puller matters, is perfectly fine with --no-ff.\n\nYeah, --no-ff would work. I guess we should find out what our \"main\ncustomers\" need here, since our own patch-based workflow is irrelevant,\nand either approach works with our \"single push authority\". Seems\n--no-ff is an easy solution to multiple sigs.\n\n> Unlike the sign-less \"we together made these history and nobody really\n> owns the result\" (aka \"Linus hates --no-ff merge because people do that to\n> leave a mark by peeing in the snow, without adding anything of value in\n> the history\"), the whole purpose of signing a commit in the scenario you\n> mentioned is for the puller to leave his mark in the history.\n\ndiff --git i/Documentation/merge-options.txt\nw/Documentation/merge-options.txt\nindex b613d4e..74d6a05 100644\n--- i/Documentation/merge-options.txt\n+++ w/Documentation/merge-options.txt\n@@ -7,6 +7,13 @@ With --no-commit perform the merge but pretend the merge\n failed and do not autocommit, to give the user a chance to\n inspect and further tweak the merge result before committing.\n\n+--pee::\n+--no-pee::\n+       This activates `--commit` and `--no-ff` and passes\n+       `--gpg-sign` to `commit`.\n++\n+Use `--no-pee` if you do not want or need to pee.\n+\n --ff::\n --no-ff::\n        Do not generate a merge commit if the merge resolved as\n\n:)\n\nMichael\n"},{"id":"177974","messageId":"1318983645-18897-1-git-send-email-gitster@pobox.com","threadId":"28598","inReplyTo":"7vaa9f3pk8.fsf@alter.siamese.dyndns.org","subject":"[PATCH v3 0/3] Signed-commit","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-10-19T00:20:42Z","receivedAt":"2011-10-19T00:20:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This is another re-roll of the signed-commit topic.\n\n - The first patch refactors where the current code invokes gpg for\n   signing and verification of tags;\n\n - The second patch introduces signed commit objects;\n\n - The third patch teaches \"git log/show\" to show the signature.\n\nThe internal API to drive \"gpg\" has been updated to better suit the\nverification side, but I suspect that it needs another round of revamp to\nslurp in the diagnostic message \"gpg\" produces to give us better control\nof the formatting of the output. e.g.\n\n    $ git show --format=\"%h %s%n%gpgsign\" -s HEAD\n\nmight want to say:\n\n    a030b7cf log: --show-signature\n    RSA key ID 96AFE6CB, Good signature from \"Junio C Hamano <gitster@pobox.com>\"\n\nor something like that, but the series is not there yet.\n\nJunio C Hamano (3):\n  Split GPG interface into its own helper library\n  commit: teach --gpg-sign option\n  log: --show-signature\n\n Makefile              |    2 +\n builtin/commit-tree.c |   24 ++++++++-\n builtin/commit.c      |   12 ++++-\n builtin/merge.c       |   16 +++++-\n builtin/tag.c         |   76 ++-------------------------\n builtin/verify-tag.c  |   36 ++-----------\n commit.c              |   74 ++++++++++++++++++++++++++-\n commit.h              |    5 ++-\n gpg-interface.c       |  137 +++++++++++++++++++++++++++++++++++++++++++++++++\n gpg-interface.h       |   10 ++++\n log-tree.c            |   17 ++++++\n notes-cache.c         |    2 +-\n notes-merge.c         |    2 +-\n revision.c            |    2 +\n revision.h            |    1 +\n tag.c                 |    5 ++\n 16 files changed, 310 insertions(+), 111 deletions(-)\n create mode 100644 gpg-interface.c\n create mode 100644 gpg-interface.h\n\n-- \n1.7.7.388.g3a4b7\n"},{"id":"177977","messageId":"1318983645-18897-2-git-send-email-gitster@pobox.com","threadId":"28598","inReplyTo":"1318983645-18897-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 1/3] Split GPG interface into its own helper library","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-10-19T00:20:43Z","receivedAt":"2011-10-19T00:20:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This mostly moves existing code from builtin/tag.c (for signing)\nand builtin/verify-tag.c (for verifying) to a new gpg-interface.c\nfile to provide a more generic library interface.\n\n - sign_buffer() takes a payload strbuf, a signature strbuf, and a signing\n   key, runs \"gpg\" to produce a detached signature for the payload, and\n   appends it to the signature strbuf. The contents of a signed tag that\n   concatenates the payload and the detached signature can be produced by\n   giving the same strbuf as payload and signature strbuf.\n\n - verify_signed_buffer() takes a payload and a detached signature as\n   <ptr, len> pairs, and runs \"gpg --verify\" to see if the payload matches\n   the signature.\n\n\"verify-tag\" (aka \"tag -v\") saved the whole tag contents as if it is a\ndetached signature, and fed gpg the payload part of the tag. It relied on\ngpg to fail when the given tag is not signed but just is annotated.  The\nupdated run_gpg_verify() function detects the lack of detached signature\nin the input, and errors out without bothering \"gpg\".\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Makefile             |    2 +\n builtin/tag.c        |   76 ++-------------------------\n builtin/verify-tag.c |   36 ++-----------\n gpg-interface.c      |  137 ++++++++++++++++++++++++++++++++++++++++++++++++++\n gpg-interface.h      |   10 ++++\n tag.c                |    5 ++\n 6 files changed, 166 insertions(+), 100 deletions(-)\n create mode 100644 gpg-interface.c\n create mode 100644 gpg-interface.h\n\ndiff --git a/Makefile b/Makefile\nindex 8d6d451..2183223 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -530,6 +530,7 @@ LIB_H += exec_cmd.h\n LIB_H += fsck.h\n LIB_H += gettext.h\n LIB_H += git-compat-util.h\n+LIB_H += gpg-interface.h\n LIB_H += graph.h\n LIB_H += grep.h\n LIB_H += hash.h\n@@ -620,6 +621,7 @@ LIB_OBJS += entry.o\n LIB_OBJS += environment.o\n LIB_OBJS += exec_cmd.o\n LIB_OBJS += fsck.o\n+LIB_OBJS += gpg-interface.o\n LIB_OBJS += graph.o\n LIB_OBJS += grep.o\n LIB_OBJS += hash.o\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 667515e..fb0d4a1 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -14,6 +14,7 @@\n #include \"parse-options.h\"\n #include \"diff.h\"\n #include \"revision.h\"\n+#include \"gpg-interface.h\"\n \n static const char * const git_tag_usage[] = {\n \t\"git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]\",\n@@ -23,8 +24,6 @@ static const char * const git_tag_usage[] = {\n \tNULL\n };\n \n-static char signingkey[1000];\n-\n struct tag_filter {\n \tconst char **patterns;\n \tint lines;\n@@ -208,60 +207,7 @@ static int verify_tag(const char *name, const char *ref,\n \n static int do_sign(struct strbuf *buffer)\n {\n-\tstruct child_process gpg;\n-\tconst char *args[4];\n-\tchar *bracket;\n-\tint len;\n-\tint i, j;\n-\n-\tif (!*signingkey) {\n-\t\tif (strlcpy(signingkey, git_committer_info(IDENT_ERROR_ON_NO_NAME),\n-\t\t\t\tsizeof(signingkey)) > sizeof(signingkey) - 1)\n-\t\t\treturn error(_(\"committer info too long.\"));\n-\t\tbracket = strchr(signingkey, '>');\n-\t\tif (bracket)\n-\t\t\tbracket[1] = '\\0';\n-\t}\n-\n-\t/* When the username signingkey is bad, program could be terminated\n-\t * because gpg exits without reading and then write gets SIGPIPE. */\n-\tsignal(SIGPIPE, SIG_IGN);\n-\n-\tmemset(&gpg, 0, sizeof(gpg));\n-\tgpg.argv = args;\n-\tgpg.in = -1;\n-\tgpg.out = -1;\n-\targs[0] = \"gpg\";\n-\targs[1] = \"-bsau\";\n-\targs[2] = signingkey;\n-\targs[3] = NULL;\n-\n-\tif (start_command(&gpg))\n-\t\treturn error(_(\"could not run gpg.\"));\n-\n-\tif (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) {\n-\t\tclose(gpg.in);\n-\t\tclose(gpg.out);\n-\t\tfinish_command(&gpg);\n-\t\treturn error(_(\"gpg did not accept the tag data\"));\n-\t}\n-\tclose(gpg.in);\n-\tlen = strbuf_read(buffer, gpg.out, 1024);\n-\tclose(gpg.out);\n-\n-\tif (finish_command(&gpg) || !len || len < 0)\n-\t\treturn error(_(\"gpg failed to sign the tag\"));\n-\n-\t/* Strip CR from the line endings, in case we are on Windows. */\n-\tfor (i = j = 0; i < buffer->len; i++)\n-\t\tif (buffer->buf[i] != '\\r') {\n-\t\t\tif (i != j)\n-\t\t\t\tbuffer->buf[j] = buffer->buf[i];\n-\t\t\tj++;\n-\t\t}\n-\tstrbuf_setlen(buffer, j);\n-\n-\treturn 0;\n+\treturn sign_buffer(buffer, buffer, get_signing_key());\n }\n \n static const char tag_template[] =\n@@ -270,21 +216,11 @@ static const char tag_template[] =\n \t\"# Write a tag message\\n\"\n \t\"#\\n\");\n \n-static void set_signingkey(const char *value)\n-{\n-\tif (strlcpy(signingkey, value, sizeof(signingkey)) >= sizeof(signingkey))\n-\t\tdie(_(\"signing key value too long (%.10s...)\"), value);\n-}\n-\n static int git_tag_config(const char *var, const char *value, void *cb)\n {\n-\tif (!strcmp(var, \"user.signingkey\")) {\n-\t\tif (!value)\n-\t\t\treturn config_error_nonbool(var);\n-\t\tset_signingkey(value);\n-\t\treturn 0;\n-\t}\n-\n+\tint status = git_gpg_config(var, value, cb);\n+\tif (status)\n+\t\treturn status;\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -463,7 +399,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \n \tif (keyid) {\n \t\tsign = 1;\n-\t\tset_signingkey(keyid);\n+\t\tset_signing_key(keyid);\n \t}\n \tif (sign)\n \t\tannotate = 1;\ndiff --git a/builtin/verify-tag.c b/builtin/verify-tag.c\nindex 3134766..4bbcf77 100644\n--- a/builtin/verify-tag.c\n+++ b/builtin/verify-tag.c\n@@ -11,6 +11,7 @@\n #include \"run-command.h\"\n #include <signal.h>\n #include \"parse-options.h\"\n+#include \"gpg-interface.h\"\n \n static const char * const verify_tag_usage[] = {\n \t\t\"git verify-tag [-v|--verbose] <tag>...\",\n@@ -19,42 +20,17 @@ static const char * const verify_tag_usage[] = {\n \n static int run_gpg_verify(const char *buf, unsigned long size, int verbose)\n {\n-\tstruct child_process gpg;\n-\tconst char *args_gpg[] = {\"gpg\", \"--verify\", \"FILE\", \"-\", NULL};\n-\tchar path[PATH_MAX];\n-\tsize_t len;\n-\tint fd, ret;\n+\tint len;\n \n-\tfd = git_mkstemp(path, PATH_MAX, \".git_vtag_tmpXXXXXX\");\n-\tif (fd < 0)\n-\t\treturn error(\"could not create temporary file '%s': %s\",\n-\t\t\t\t\t\tpath, strerror(errno));\n-\tif (write_in_full(fd, buf, size) < 0)\n-\t\treturn error(\"failed writing temporary file '%s': %s\",\n-\t\t\t\t\t\tpath, strerror(errno));\n-\tclose(fd);\n-\n-\t/* find the length without signature */\n \tlen = parse_signature(buf, size);\n \tif (verbose)\n \t\twrite_in_full(1, buf, len);\n \n-\tmemset(&gpg, 0, sizeof(gpg));\n-\tgpg.argv = args_gpg;\n-\tgpg.in = -1;\n-\targs_gpg[2] = path;\n-\tif (start_command(&gpg)) {\n-\t\tunlink(path);\n-\t\treturn error(\"could not run gpg.\");\n-\t}\n-\n-\twrite_in_full(gpg.in, buf, len);\n-\tclose(gpg.in);\n-\tret = finish_command(&gpg);\n+\tif (size == len)\n+\t\treturn error(\"no signature found\");\n \n-\tunlink_or_warn(path);\n-\n-\treturn ret;\n+\treturn verify_signed_buffer(buf, len,\n+\t\t\t\t    buf + len, size - len, 0);\n }\n \n static int verify_tag(const char *name, int verbose)\ndiff --git a/gpg-interface.c b/gpg-interface.c\nnew file mode 100644\nindex 0000000..62f3806\n--- /dev/null\n+++ b/gpg-interface.c\n@@ -0,0 +1,137 @@\n+#include \"cache.h\"\n+#include \"run-command.h\"\n+#include \"strbuf.h\"\n+#include \"gpg-interface.h\"\n+#include \"sigchain.h\"\n+\n+static char *configured_signing_key;\n+\n+void set_signing_key(const char *key)\n+{\n+\tfree(configured_signing_key);\n+\tconfigured_signing_key = xstrdup(key);\n+}\n+\n+int git_gpg_config(const char *var, const char *value, void *cb)\n+{\n+\tif (!strcmp(var, \"user.signingkey\")) {\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tset_signing_key(value);\n+\t}\n+\treturn 0;\n+}\n+\n+const char *get_signing_key(void)\n+{\n+\tif (configured_signing_key)\n+\t\treturn configured_signing_key;\n+\treturn git_committer_info(IDENT_ERROR_ON_NO_NAME|IDENT_NO_DATE);\n+}\n+\n+/*\n+ * Create a detached signature for the contents of \"buffer\" and append\n+ * it after \"signature\"; \"buffer\" and \"signature\" can be the same\n+ * strbuf instance, which would cause the detached signature appended\n+ * at the end.\n+ */\n+int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key)\n+{\n+\tstruct child_process gpg;\n+\tconst char *args[4];\n+\tssize_t len;\n+\tsize_t i, j, bottom;\n+\n+\tmemset(&gpg, 0, sizeof(gpg));\n+\tgpg.argv = args;\n+\tgpg.in = -1;\n+\tgpg.out = -1;\n+\targs[0] = \"gpg\";\n+\targs[1] = \"-bsau\";\n+\targs[2] = signing_key;\n+\targs[3] = NULL;\n+\n+\tif (start_command(&gpg))\n+\t\treturn error(_(\"could not run gpg.\"));\n+\n+\t/*\n+\t * When the username signingkey is bad, program could be terminated\n+\t * because gpg exits without reading and then write gets SIGPIPE.\n+\t */\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\n+\tif (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) {\n+\t\tclose(gpg.in);\n+\t\tclose(gpg.out);\n+\t\tfinish_command(&gpg);\n+\t\treturn error(_(\"gpg did not accept the data\"));\n+\t}\n+\tclose(gpg.in);\n+\n+\tbottom = signature->len;\n+\tlen = strbuf_read(signature, gpg.out, 1024);\n+\tclose(gpg.out);\n+\n+\tsigchain_pop(SIGPIPE);\n+\n+\tif (finish_command(&gpg) || !len || len < 0)\n+\t\treturn error(_(\"gpg failed to sign the data\"));\n+\n+\t/* Strip CR from the line endings, in case we are on Windows. */\n+\tfor (i = j = bottom; i < signature->len; i++)\n+\t\tif (signature->buf[i] != '\\r') {\n+\t\t\tif (i != j)\n+\t\t\t\tsignature->buf[j] = signature->buf[i];\n+\t\t\tj++;\n+\t\t}\n+\tstrbuf_setlen(signature, j);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Run \"gpg\" to see if the payload matches the detached signature.\n+ * gpg_output_to tells where the output from \"gpg\" should go:\n+ *   < 0: /dev/null\n+ *   = 0: standard error of the calling process\n+ *   > 0: the specified file descriptor\n+ */\n+int verify_signed_buffer(const char *payload, size_t payload_size,\n+\t\t\t const char *signature, size_t signature_size,\n+\t\t\t int gpg_output_to)\n+{\n+\tstruct child_process gpg;\n+\tconst char *args_gpg[] = {\"gpg\", \"--verify\", \"FILE\", \"-\", NULL};\n+\tchar path[PATH_MAX];\n+\tint fd, ret;\n+\n+\tfd = git_mkstemp(path, PATH_MAX, \".git_vtag_tmpXXXXXX\");\n+\tif (fd < 0)\n+\t\treturn error(\"could not create temporary file '%s': %s\",\n+\t\t\t     path, strerror(errno));\n+\tif (write_in_full(fd, signature, signature_size) < 0)\n+\t\treturn error(\"failed writing detached signature to '%s': %s\",\n+\t\t\t     path, strerror(errno));\n+\tclose(fd);\n+\n+\tmemset(&gpg, 0, sizeof(gpg));\n+\tgpg.argv = args_gpg;\n+\tgpg.in = -1;\n+\tif (gpg_output_to < 0)\n+\t\tgpg.no_stderr = 1;\n+\telse\n+\t\tgpg.err = gpg_output_to;\n+\targs_gpg[2] = path;\n+\tif (start_command(&gpg)) {\n+\t\tunlink(path);\n+\t\treturn error(\"could not run gpg.\");\n+\t}\n+\n+\twrite_in_full(gpg.in, payload, payload_size);\n+\tclose(gpg.in);\n+\tret = finish_command(&gpg);\n+\n+\tunlink_or_warn(path);\n+\n+\treturn ret;\n+}\ndiff --git a/gpg-interface.h b/gpg-interface.h\nnew file mode 100644\nindex 0000000..df58bb9\n--- /dev/null\n+++ b/gpg-interface.h\n@@ -0,0 +1,10 @@\n+#ifndef GPG_INTERFACE_H\n+#define GPG_INTERFACE_H\n+\n+extern int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key);\n+extern int verify_signed_buffer(const char *payload, size_t payload_size, const char *signature, size_t signature_size, int gpg_output_to);\n+extern int git_gpg_config(const char *, const char *, void *);\n+extern void set_signing_key(const char *);\n+extern const char *get_signing_key(void);\n+\n+#endif\ndiff --git a/tag.c b/tag.c\nindex 7d38cc0..3aa186d 100644\n--- a/tag.c\n+++ b/tag.c\n@@ -139,6 +139,11 @@ int parse_tag(struct tag *item)\n \treturn ret;\n }\n \n+/*\n+ * Look at a signed tag object, and return the offset where\n+ * the embedded detached signature begins, or the end of the\n+ * data when there is no such signature.\n+ */\n size_t parse_signature(const char *buf, unsigned long size)\n {\n \tchar *eol;\n-- \n1.7.7.388.g3a4b7\n"},{"id":"177976","messageId":"1318983645-18897-3-git-send-email-gitster@pobox.com","threadId":"28598","inReplyTo":"1318983645-18897-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 2/3] commit: teach --gpg-sign option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-10-19T00:20:44Z","receivedAt":"2011-10-19T00:20:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"And this uses the gpg-interface.[ch] to allow signing the commit, i.e.\n\n    $ git commit --gpg-sign -m foo\n    You need a passphrase to unlock the secret key for\n    user: \"Junio C Hamano <gitster@pobox.com>\"\n    4096-bit RSA key, ID 96AFE6CB, created 2011-10-03 (main key ID 713660A7)\n\n    [master 8457d13] foo\n     1 files changed, 1 insertions(+), 0 deletions(-)\n\nThe lines of GPG detached signature are placed in new header lines, after\nthe standard tree/parent/author/committer headers, instead of tucking the\nsignature block at the end of the commit log message text (similar to how\nsigned tag is done), for multiple reasons:\n\n - The signature won't clutter output from \"git log\" and friends if it is\n   in the extra header. If we place it at the end of the log message, we\n   would need to teach \"git log\" and friends to strip the signature block\n   with an option.\n\n - Teaching new versions of \"git log\" and \"gitk\" to optionally verify and\n   show signatures is cleaner if we structurally know where the signature\n   block is (instead of scanning in the commit log message).\n\n - The signature needs to be stripped upon various commit rewriting\n   operations, e.g. rebase, filter-branch, etc. They all already ignore\n   unknown headers, but if we place signature in the log message, all of\n   these tools (and third-party tools) also need to learn how a signature\n   block would look like.\n\n - When we added the optional encoding header, all the tools (both in tree\n   and third-party) that acts on the raw commit object should have been\n   fixed to ignore headers they do not understand, so it is not like that\n   new header would be more likely to break than extra text in the commit.\n\nA commit made with the above sample sequence would look like this:\n\n    $ git cat-file commit HEAD\n    tree 3cd71d90e3db4136e5260ab54599791c4f883b9d\n    parent b87755351a47b09cb27d6913e6e0e17e6254a4d4\n    author Junio C Hamano <gitster@pobox.com> 1317862251 -0700\n    committer Junio C Hamano <gitster@pobox.com> 1317862251 -0700\n    gpgsig -----BEGIN PGP SIGNATURE-----\n    gpgsig Version: GnuPG v1.4.10 (GNU/Linux)\n    gpgsig\n    gpgsig iQIcBAABAgAGBQJOjPtrAAoJELC16IaWr+bL4TMP/RSe2Y/jYnCkds9unO5JEnfG\n    gpgsig ...\n    gpgsig =dt98\n    gpgsig -----END PGP SIGNATURE-----\n\n    foo\n\nbut \"git log\" (unless you ask for it with --pretty=raw) output is not\ncluttered with the signature information.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n\n * The header field was updated from \"sig\" to \"gpgsig\" so that later we\n   won't have to regret the choice when we introduce different kind of\n   signature mechanism.\n\n builtin/commit-tree.c |   24 +++++++++++++++++++++---\n builtin/commit.c      |   12 ++++++++++--\n builtin/merge.c       |   16 ++++++++++++++--\n commit.c              |   40 +++++++++++++++++++++++++++++++++++++++-\n commit.h              |    2 +-\n notes-cache.c         |    2 +-\n notes-merge.c         |    2 +-\n 7 files changed, 87 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/commit-tree.c b/builtin/commit-tree.c\nindex d083795..a17811f 100644\n--- a/builtin/commit-tree.c\n+++ b/builtin/commit-tree.c\n@@ -8,8 +8,9 @@\n #include \"tree.h\"\n #include \"builtin.h\"\n #include \"utf8.h\"\n+#include \"gpg-interface.h\"\n \n-static const char commit_tree_usage[] = \"git commit-tree <sha1> [(-p <sha1>)...] < changelog\";\n+static const char commit_tree_usage[] = \"git commit-tree [-S<signer>] <sha1> [(-p <sha1>)...] < changelog\";\n \n static void new_parent(struct commit *parent, struct commit_list **parents_p)\n {\n@@ -25,6 +26,14 @@ static void new_parent(struct commit *parent, struct commit_list **parents_p)\n \tcommit_list_insert(parent, parents_p);\n }\n \n+static int commit_tree_config(const char *var, const char *value, void *cb)\n+{\n+\tint status = git_gpg_config(var, value, NULL);\n+\tif (status)\n+\t\treturn status;\n+\treturn git_default_config(var, value, cb);\n+}\n+\n int cmd_commit_tree(int argc, const char **argv, const char *prefix)\n {\n \tint i;\n@@ -32,11 +41,19 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)\n \tunsigned char tree_sha1[20];\n \tunsigned char commit_sha1[20];\n \tstruct strbuf buffer = STRBUF_INIT;\n+\tconst char *sign_commit = NULL;\n \n-\tgit_config(git_default_config, NULL);\n+\tgit_config(commit_tree_config, NULL);\n \n \tif (argc < 2 || !strcmp(argv[1], \"-h\"))\n \t\tusage(commit_tree_usage);\n+\n+\tif (!memcmp(argv[1], \"-S\", 2)) {\n+\t\tsign_commit = argv[1] + 2;\n+\t\targv++;\n+\t\targc--;\n+\t}\n+\n \tif (get_sha1(argv[1], tree_sha1))\n \t\tdie(\"Not a valid object name %s\", argv[1]);\n \n@@ -56,7 +73,8 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)\n \tif (strbuf_read(&buffer, 0, 0) < 0)\n \t\tdie_errno(\"git commit-tree: failed to read\");\n \n-\tif (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) {\n+\tif (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1,\n+\t\t\tNULL, sign_commit)) {\n \t\tstrbuf_release(&buffer);\n \t\treturn 1;\n \t}\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex cbc9613..90cf7e8 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -26,6 +26,7 @@\n #include \"unpack-trees.h\"\n #include \"quote.h\"\n #include \"submodule.h\"\n+#include \"gpg-interface.h\"\n \n static const char * const builtin_commit_usage[] = {\n \t\"git commit [options] [--] <filepattern>...\",\n@@ -85,6 +86,8 @@ static int all, edit_flag, also, interactive, patch_interactive, only, amend, si\n static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;\n static int no_post_rewrite, allow_empty_message;\n static char *untracked_files_arg, *force_date, *ignore_submodule_arg;\n+static char *sign_commit;\n+\n /*\n  * The default commit message cleanup mode will remove the lines\n  * beginning with # (shell comments) and leading and trailing\n@@ -144,6 +147,8 @@ static struct option builtin_commit_options[] = {\n \tOPT_BOOLEAN('e', \"edit\", &edit_flag, \"force edit of commit\"),\n \tOPT_STRING(0, \"cleanup\", &cleanup_arg, \"default\", \"how to strip spaces and #comments from message\"),\n \tOPT_BOOLEAN(0, \"status\", &include_status, \"include status in commit message template\"),\n+\t{ OPTION_STRING, 'S', \"gpg-sign\", &sign_commit, \"key id\",\n+\t  \"GPG sign commit\", PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n \t/* end commit message options */\n \n \tOPT_GROUP(\"Commit contents options\"),\n@@ -1323,6 +1328,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1,\n static int git_commit_config(const char *k, const char *v, void *cb)\n {\n \tstruct wt_status *s = cb;\n+\tint status;\n \n \tif (!strcmp(k, \"commit.template\"))\n \t\treturn git_config_pathname(&template_file, k, v);\n@@ -1330,7 +1336,9 @@ static int git_commit_config(const char *k, const char *v, void *cb)\n \t\tinclude_status = git_config_bool(k, v);\n \t\treturn 0;\n \t}\n-\n+\tstatus = git_gpg_config(k, v, NULL);\n+\tif (status)\n+\t\treturn status;\n \treturn git_status_config(k, v, s);\n }\n \n@@ -1481,7 +1489,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t}\n \n \tif (commit_tree(sb.buf, active_cache_tree->sha1, parents, sha1,\n-\t\t\tauthor_ident.buf)) {\n+\t\t\tauthor_ident.buf, sign_commit)) {\n \t\trollback_index_files();\n \t\tdie(_(\"failed to write commit object\"));\n \t}\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex ab4077f..53cff02 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -26,6 +26,7 @@\n #include \"merge-recursive.h\"\n #include \"resolve-undo.h\"\n #include \"remote.h\"\n+#include \"gpg-interface.h\"\n \n #define DEFAULT_TWOHEAD (1<<0)\n #define DEFAULT_OCTOPUS (1<<1)\n@@ -63,6 +64,7 @@ static int allow_rerere_auto;\n static int abort_current_merge;\n static int show_progress = -1;\n static int default_to_upstream;\n+static const char *sign_commit;\n \n static struct strategy all_strategy[] = {\n \t{ \"recursive\",  DEFAULT_TWOHEAD | NO_TRIVIAL },\n@@ -206,6 +208,8 @@ static struct option builtin_merge_options[] = {\n \tOPT_BOOLEAN(0, \"abort\", &abort_current_merge,\n \t\t\"abort the current in-progress merge\"),\n \tOPT_SET_INT(0, \"progress\", &show_progress, \"force progress reporting\", 1),\n+\t{ OPTION_STRING, 'S', \"gpg-sign\", &sign_commit, \"key id\",\n+\t  \"GPG sign commit\", PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n \tOPT_END()\n };\n \n@@ -525,6 +529,8 @@ static void parse_branch_merge_options(char *bmo)\n \n static int git_merge_config(const char *k, const char *v, void *cb)\n {\n+\tint status;\n+\n \tif (branch && !prefixcmp(k, \"branch.\") &&\n \t\t!prefixcmp(k + 7, branch) &&\n \t\t!strcmp(k + 7 + strlen(branch), \".mergeoptions\")) {\n@@ -562,6 +568,10 @@ static int git_merge_config(const char *k, const char *v, void *cb)\n \t\tdefault_to_upstream = git_config_bool(k, v);\n \t\treturn 0;\n \t}\n+\n+\tstatus = git_gpg_config(k, v, NULL);\n+\tif (status)\n+\t\treturn status;\n \treturn git_diff_ui_config(k, v, cb);\n }\n \n@@ -870,7 +880,8 @@ static int merge_trivial(void)\n \tparent->next->item = remoteheads->item;\n \tparent->next->next = NULL;\n \trun_prepare_commit_msg();\n-\tcommit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);\n+\tcommit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL,\n+\t\t    sign_commit);\n \tfinish(result_commit, \"In-index merge\");\n \tdrop_save();\n \treturn 0;\n@@ -900,7 +911,8 @@ static int finish_automerge(struct commit_list *common,\n \tfree_commit_list(remoteheads);\n \tstrbuf_addch(&merge_msg, '\\n');\n \trun_prepare_commit_msg();\n-\tcommit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);\n+\tcommit_tree(merge_msg.buf, result_tree, parents, result_commit,\n+\t\t    NULL, sign_commit);\n \tstrbuf_addf(&buf, \"Merge made by the '%s' strategy.\", wt_strategy);\n \tfinish(result_commit, buf.buf);\n \tstrbuf_release(&buf);\ndiff --git a/commit.c b/commit.c\nindex 97b4327..4bff3cd 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -6,6 +6,7 @@\n #include \"diff.h\"\n #include \"revision.h\"\n #include \"notes.h\"\n+#include \"gpg-interface.h\"\n \n int save_commit_buffer = 1;\n \n@@ -814,6 +815,40 @@ struct commit_list *reduce_heads(struct commit_list *heads)\n \treturn result;\n }\n \n+static const char gpg_sig_header[] = \"gpgsig \";\n+static const int gpg_sig_header_len = sizeof(gpg_sig_header) - 1;\n+\n+static int do_sign_commit(struct strbuf *buf, const char *keyid)\n+{\n+\tstruct strbuf sig = STRBUF_INIT;\n+\tint inspos, copypos;\n+\n+\t/* find the end of the header */\n+\tinspos = strstr(buf->buf, \"\\n\\n\") - buf->buf + 1;\n+\n+\tif (!keyid || !*keyid)\n+\t\tkeyid = get_signing_key();\n+\tif (sign_buffer(buf, &sig, keyid)) {\n+\t\tstrbuf_release(&sig);\n+\t\treturn -1;\n+\t}\n+\n+\tfor (copypos = 0; sig.buf[copypos]; ) {\n+\t\tconst char *bol = sig.buf + copypos;\n+\t\tconst char *eol = strchrnul(bol, '\\n');\n+\t\tint len = (eol - bol) + !!*eol;\n+\n+\t\tstrbuf_insert(buf, inspos, gpg_sig_header, gpg_sig_header_len);\n+\t\tinspos += gpg_sig_header_len;\n+\t\tstrbuf_insert(buf, inspos, bol, len);\n+\t\tinspos += len;\n+\t\tcopypos += len;\n+\t}\n+\tstrbuf_release(&sig);\n+\treturn 0;\n+}\n+\n+\n static const char commit_utf8_warn[] =\n \"Warning: commit message does not conform to UTF-8.\\n\"\n \"You may want to amend it after fixing the message, or set the config\\n\"\n@@ -821,7 +856,7 @@ static const char commit_utf8_warn[] =\n \n int commit_tree(const char *msg, unsigned char *tree,\n \t\tstruct commit_list *parents, unsigned char *ret,\n-\t\tconst char *author)\n+\t\tconst char *author, const char *sign_commit)\n {\n \tint result;\n \tint encoding_is_utf8;\n@@ -864,6 +899,9 @@ int commit_tree(const char *msg, unsigned char *tree,\n \tif (encoding_is_utf8 && !is_utf8(buffer.buf))\n \t\tfprintf(stderr, commit_utf8_warn);\n \n+\tif (sign_commit && do_sign_commit(&buffer, sign_commit))\n+\t\treturn -1;\n+\n \tresult = write_sha1_file(buffer.buf, buffer.len, commit_type, ret);\n \tstrbuf_release(&buffer);\n \treturn result;\ndiff --git a/commit.h b/commit.h\nindex 12d100b8..8c2419b 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -175,6 +175,6 @@ struct commit_list *reduce_heads(struct commit_list *heads);\n \n extern int commit_tree(const char *msg, unsigned char *tree,\n \t\tstruct commit_list *parents, unsigned char *ret,\n-\t\tconst char *author);\n+\t\t       const char *author, const char *sign_commit);\n \n #endif /* COMMIT_H */\ndiff --git a/notes-cache.c b/notes-cache.c\nindex 4c8984e..c36a960 100644\n--- a/notes-cache.c\n+++ b/notes-cache.c\n@@ -56,7 +56,7 @@ int notes_cache_write(struct notes_cache *c)\n \n \tif (write_notes_tree(&c->tree, tree_sha1))\n \t\treturn -1;\n-\tif (commit_tree(c->validity, tree_sha1, NULL, commit_sha1, NULL) < 0)\n+\tif (commit_tree(c->validity, tree_sha1, NULL, commit_sha1, NULL, NULL) < 0)\n \t\treturn -1;\n \tif (update_ref(\"update notes cache\", c->tree.ref, commit_sha1, NULL,\n \t\t       0, QUIET_ON_ERR) < 0)\ndiff --git a/notes-merge.c b/notes-merge.c\nindex e1aaf43..c29c434 100644\n--- a/notes-merge.c\n+++ b/notes-merge.c\n@@ -546,7 +546,7 @@ void create_notes_commit(struct notes_tree *t, struct commit_list *parents,\n \t\t/* else: t->ref points to nothing, assume root/orphan commit */\n \t}\n \n-\tif (commit_tree(msg, tree_sha1, parents, result_sha1, NULL))\n+\tif (commit_tree(msg, tree_sha1, parents, result_sha1, NULL, NULL))\n \t\tdie(\"Failed to commit notes tree to database\");\n }\n \n-- \n1.7.7.388.g3a4b7\n"},{"id":"177975","messageId":"1318983645-18897-4-git-send-email-gitster@pobox.com","threadId":"28598","inReplyTo":"1318983645-18897-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 3/3] log: --show-signature","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-10-19T00:20:45Z","receivedAt":"2011-10-19T00:20:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This teaches the \"log\" family of commands to pass the GPG signature in the\ncommit objects to \"gpg --verify\" via the verify_signed_buffer() interface\nused to verify signed tag objects. E.g.\n\n    $ git show --show-signature -s HEAD\n\nwould show GPG output in the header part of the output.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n\n * We may want to capture good/bad/untrusted signature information from\n   GPG and teach userformat to show that, but this code is not there yet.\n\n commit.c   |   34 ++++++++++++++++++++++++++++++++++\n commit.h   |    3 +++\n log-tree.c |   17 +++++++++++++++++\n revision.c |    2 ++\n revision.h |    1 +\n 5 files changed, 57 insertions(+), 0 deletions(-)\n\ndiff --git a/commit.c b/commit.c\nindex 4bff3cd..93045a2 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -848,6 +848,40 @@ static int do_sign_commit(struct strbuf *buf, const char *keyid)\n \treturn 0;\n }\n \n+int parse_signed_commit(const unsigned char *sha1,\n+\t\t\tstruct strbuf *payload, struct strbuf *signature)\n+{\n+\tunsigned long size;\n+\tenum object_type type;\n+\tchar *buffer = read_sha1_file(sha1, &type, &size);\n+\tint in_header, saw_signature = -1;\n+\tchar *line;\n+\n+\tif (!buffer || type != OBJ_COMMIT)\n+\t\tgoto cleanup;\n+\n+\tline = buffer;\n+\tin_header = 1;\n+\tsaw_signature = 0;\n+\twhile (*line) {\n+\t\tchar *next = strchrnul(line, '\\n');\n+\t\tif (*next)\n+\t\t\tnext++;\n+\t\tif (in_header && !prefixcmp(line, gpg_sig_header)) {\n+\t\t\tconst char *sig = line + gpg_sig_header_len;\n+\t\t\tstrbuf_add(signature, sig, next - sig);\n+\t\t\tsaw_signature = 1;\n+\t\t} else {\n+\t\t\tstrbuf_add(payload, line, next - line);\n+\t\t}\n+\t\tif (*line == '\\n')\n+\t\t\tin_header = 0;\n+\t\tline = next;\n+\t}\n+ cleanup:\n+\tfree(buffer);\n+\treturn saw_signature;\n+}\n \n static const char commit_utf8_warn[] =\n \"Warning: commit message does not conform to UTF-8.\\n\"\ndiff --git a/commit.h b/commit.h\nindex 8c2419b..1885471 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -177,4 +177,7 @@ extern int commit_tree(const char *msg, unsigned char *tree,\n \t\tstruct commit_list *parents, unsigned char *ret,\n \t\t       const char *author, const char *sign_commit);\n \n+extern int parse_signed_commit(const unsigned char *sha1,\n+\t\t\t       struct strbuf *message, struct strbuf *signature);\n+\n #endif /* COMMIT_H */\ndiff --git a/log-tree.c b/log-tree.c\nindex 24c295e..749bb65 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -8,6 +8,7 @@\n #include \"refs.h\"\n #include \"string-list.h\"\n #include \"color.h\"\n+#include \"gpg-interface.h\"\n \n struct decoration name_decoration = { \"object names\" };\n \n@@ -395,6 +396,19 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit,\n \t*extra_headers_p = extra_headers;\n }\n \n+static void show_signature(struct rev_info *opt, struct commit *commit)\n+{\n+\tstruct strbuf payload = STRBUF_INIT;\n+\tstruct strbuf signature = STRBUF_INIT;\n+\n+\tif (parse_signed_commit(commit->object.sha1, &payload, &signature) >= 0) {\n+\t\tverify_signed_buffer(payload.buf, payload.len,\n+\t\t\t\t     signature.buf, signature.len, 0);\n+\t}\n+\tstrbuf_release(&payload);\n+\tstrbuf_release(&signature);\n+}\n+\n void show_log(struct rev_info *opt)\n {\n \tstruct strbuf msgbuf = STRBUF_INIT;\n@@ -502,6 +516,9 @@ void show_log(struct rev_info *opt)\n \t\t}\n \t}\n \n+\tif (opt->show_signature)\n+\t\tshow_signature(opt, commit);\n+\n \tif (!commit->buffer)\n \t\treturn;\n \ndiff --git a/revision.c b/revision.c\nindex c46cfaa..860a312 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1381,6 +1381,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg\n \t\trevs->show_notes = 1;\n \t\trevs->show_notes_given = 1;\n \t\trevs->notes_opt.use_default_notes = 1;\n+\t} else if (!strcmp(arg, \"--show-signature\")) {\n+\t\trevs->show_signature = 1;\n \t} else if (!prefixcmp(arg, \"--show-notes=\") ||\n \t\t   !prefixcmp(arg, \"--notes=\")) {\n \t\tstruct strbuf buf = STRBUF_INIT;\ndiff --git a/revision.h b/revision.h\nindex 3d64ada..198bb95 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -89,6 +89,7 @@ struct rev_info {\n \t\t\tshow_merge:1,\n \t\t\tshow_notes:1,\n \t\t\tshow_notes_given:1,\n+\t\t\tshow_signature:1,\n \t\t\tpretty_given:1,\n \t\t\tabbrev_commit:1,\n \t\t\tabbrev_commit_given:1,\n-- \n1.7.7.388.g3a4b7\n"},{"id":"178056","messageId":"1319071023-31919-1-git-send-email-gitster@pobox.com","threadId":"28598","inReplyTo":"1318983645-18897-1-git-send-email-gitster@pobox.com","subject":"[PATCH v4 0/5] Signed-commit","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-10-20T00:36:58Z","receivedAt":"2011-10-20T00:36:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The fourth iteration.\n\n - The first patch refactors where the current code invokes gpg for\n   signing and verification of tags;\n\n - The second patch introduces signed commit objects (unchanged);\n\n - The third patch teaches \"git log/show\" to show the signature (adjusted\n   to the updated \"gpg output capture\" API, with colors);\n\n - The fourth patch refactors helper bits to test GPG related features\n   out of existing \"tag -s\" && \"tag --verify\" test script (new); and\n\n - The fifth patch adds tests for \"commit [--amend] -S\", \"merge -S\",\n   and \"show --show-signature\" (new).\n\nI think the internal API to drive \"gpg\" in this iteration is good to go.\nThe verification side has been updated to allow the callers to capture\noutput from GPG in a strbuf, so that they can pretty-print it in a format\nmore suitable in their context, rather than just spewing it out to the\nstandard error stream.\n\nJunio C Hamano (5):\n  Split GPG interface into its own helper library\n  commit: teach --gpg-sign option\n  log: --show-signature\n  t7004: extract generic \"GPG testing\" bits\n  test \"commit -S\" and \"log --show-signature\"\n\n Makefile                 |    2 +\n builtin/commit-tree.c    |   24 +++++++-\n builtin/commit.c         |   12 +++-\n builtin/merge.c          |   16 +++++-\n builtin/tag.c            |   76 ++-----------------------\n builtin/verify-tag.c     |   35 ++----------\n commit.c                 |   74 ++++++++++++++++++++++++-\n commit.h                 |    5 +-\n gpg-interface.c          |  138 ++++++++++++++++++++++++++++++++++++++++++++++\n gpg-interface.h          |   10 +++\n log-tree.c               |   39 +++++++++++++\n notes-cache.c            |    2 +-\n notes-merge.c            |    2 +-\n revision.c               |    2 +\n revision.h               |    1 +\n t/lib-gpg.sh             |   29 ++++++++++\n t/lib-gpg/pubring.gpg    |  Bin 0 -> 1164 bytes\n t/lib-gpg/random_seed    |  Bin 0 -> 600 bytes\n t/lib-gpg/secring.gpg    |  Bin 0 -> 1237 bytes\n t/lib-gpg/trustdb.gpg    |  Bin 0 -> 1280 bytes\n t/t7004-tag.sh           |   29 +---------\n t/t7004/pubring.gpg      |  Bin 1164 -> 0 bytes\n t/t7004/random_seed      |  Bin 600 -> 0 bytes\n t/t7004/secring.gpg      |  Bin 1237 -> 0 bytes\n t/t7004/trustdb.gpg      |  Bin 1280 -> 0 bytes\n t/t7510-signed-commit.sh |   60 ++++++++++++++++++++\n tag.c                    |    5 ++\n 27 files changed, 422 insertions(+), 139 deletions(-)\n create mode 100644 gpg-interface.c\n create mode 100644 gpg-interface.h\n create mode 100644 t/lib-gpg.sh\n create mode 100644 t/lib-gpg/pubring.gpg\n create mode 100644 t/lib-gpg/random_seed\n create mode 100644 t/lib-gpg/secring.gpg\n create mode 100644 t/lib-gpg/trustdb.gpg\n delete mode 100644 t/t7004/pubring.gpg\n delete mode 100644 t/t7004/random_seed\n delete mode 100644 t/t7004/secring.gpg\n delete mode 100644 t/t7004/trustdb.gpg\n create mode 100755 t/t7510-signed-commit.sh\n\n-- \n1.7.7.498.g3f2e50\n"},{"id":"178055","messageId":"1319071023-31919-2-git-send-email-gitster@pobox.com","threadId":"28598","inReplyTo":"1319071023-31919-1-git-send-email-gitster@pobox.com","subject":"[PATCH v4 1/5] Split GPG interface into its own helper library","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-10-20T00:36:59Z","receivedAt":"2011-10-20T00:36:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This mostly moves existing code from builtin/tag.c (for signing)\nand builtin/verify-tag.c (for verifying) to a new gpg-interface.c\nfile to provide a more generic library interface.\n\n - sign_buffer() takes a payload strbuf, a signature strbuf, and a signing\n   key, runs \"gpg\" to produce a detached signature for the payload, and\n   appends it to the signature strbuf. The contents of a signed tag that\n   concatenates the payload and the detached signature can be produced by\n   giving the same strbuf as payload and signature strbuf.\n\n - verify_signed_buffer() takes a payload and a detached signature as\n   <ptr, len> pairs, and runs \"gpg --verify\" to see if the payload matches\n   the signature. It can optionally capture the output from GPG to allow\n   the callers to pretty-print it in a way more suitable for their\n   contexts.\n\n\"verify-tag\" (aka \"tag -v\") used to save the whole tag contents as if it\nis a detached signature, and fed gpg the payload part of the tag. It\nrelied on gpg to fail when the given tag is not signed but just is\nannotated.  The updated run_gpg_verify() function detects the lack of\ndetached signature in the input, and errors out without bothering \"gpg\".\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Makefile             |    2 +\n builtin/tag.c        |   76 ++-------------------------\n builtin/verify-tag.c |   35 ++-----------\n gpg-interface.c      |  138 ++++++++++++++++++++++++++++++++++++++++++++++++++\n gpg-interface.h      |   10 ++++\n tag.c                |    5 ++\n 6 files changed, 166 insertions(+), 100 deletions(-)\n create mode 100644 gpg-interface.c\n create mode 100644 gpg-interface.h\n\ndiff --git a/Makefile b/Makefile\nindex 8d6d451..2183223 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -530,6 +530,7 @@ LIB_H += exec_cmd.h\n LIB_H += fsck.h\n LIB_H += gettext.h\n LIB_H += git-compat-util.h\n+LIB_H += gpg-interface.h\n LIB_H += graph.h\n LIB_H += grep.h\n LIB_H += hash.h\n@@ -620,6 +621,7 @@ LIB_OBJS += entry.o\n LIB_OBJS += environment.o\n LIB_OBJS += exec_cmd.o\n LIB_OBJS += fsck.o\n+LIB_OBJS += gpg-interface.o\n LIB_OBJS += graph.o\n LIB_OBJS += grep.o\n LIB_OBJS += hash.o\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 667515e..fb0d4a1 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -14,6 +14,7 @@\n #include \"parse-options.h\"\n #include \"diff.h\"\n #include \"revision.h\"\n+#include \"gpg-interface.h\"\n \n static const char * const git_tag_usage[] = {\n \t\"git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]\",\n@@ -23,8 +24,6 @@ static const char * const git_tag_usage[] = {\n \tNULL\n };\n \n-static char signingkey[1000];\n-\n struct tag_filter {\n \tconst char **patterns;\n \tint lines;\n@@ -208,60 +207,7 @@ static int verify_tag(const char *name, const char *ref,\n \n static int do_sign(struct strbuf *buffer)\n {\n-\tstruct child_process gpg;\n-\tconst char *args[4];\n-\tchar *bracket;\n-\tint len;\n-\tint i, j;\n-\n-\tif (!*signingkey) {\n-\t\tif (strlcpy(signingkey, git_committer_info(IDENT_ERROR_ON_NO_NAME),\n-\t\t\t\tsizeof(signingkey)) > sizeof(signingkey) - 1)\n-\t\t\treturn error(_(\"committer info too long.\"));\n-\t\tbracket = strchr(signingkey, '>');\n-\t\tif (bracket)\n-\t\t\tbracket[1] = '\\0';\n-\t}\n-\n-\t/* When the username signingkey is bad, program could be terminated\n-\t * because gpg exits without reading and then write gets SIGPIPE. */\n-\tsignal(SIGPIPE, SIG_IGN);\n-\n-\tmemset(&gpg, 0, sizeof(gpg));\n-\tgpg.argv = args;\n-\tgpg.in = -1;\n-\tgpg.out = -1;\n-\targs[0] = \"gpg\";\n-\targs[1] = \"-bsau\";\n-\targs[2] = signingkey;\n-\targs[3] = NULL;\n-\n-\tif (start_command(&gpg))\n-\t\treturn error(_(\"could not run gpg.\"));\n-\n-\tif (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) {\n-\t\tclose(gpg.in);\n-\t\tclose(gpg.out);\n-\t\tfinish_command(&gpg);\n-\t\treturn error(_(\"gpg did not accept the tag data\"));\n-\t}\n-\tclose(gpg.in);\n-\tlen = strbuf_read(buffer, gpg.out, 1024);\n-\tclose(gpg.out);\n-\n-\tif (finish_command(&gpg) || !len || len < 0)\n-\t\treturn error(_(\"gpg failed to sign the tag\"));\n-\n-\t/* Strip CR from the line endings, in case we are on Windows. */\n-\tfor (i = j = 0; i < buffer->len; i++)\n-\t\tif (buffer->buf[i] != '\\r') {\n-\t\t\tif (i != j)\n-\t\t\t\tbuffer->buf[j] = buffer->buf[i];\n-\t\t\tj++;\n-\t\t}\n-\tstrbuf_setlen(buffer, j);\n-\n-\treturn 0;\n+\treturn sign_buffer(buffer, buffer, get_signing_key());\n }\n \n static const char tag_template[] =\n@@ -270,21 +216,11 @@ static const char tag_template[] =\n \t\"# Write a tag message\\n\"\n \t\"#\\n\");\n \n-static void set_signingkey(const char *value)\n-{\n-\tif (strlcpy(signingkey, value, sizeof(signingkey)) >= sizeof(signingkey))\n-\t\tdie(_(\"signing key value too long (%.10s...)\"), value);\n-}\n-\n static int git_tag_config(const char *var, const char *value, void *cb)\n {\n-\tif (!strcmp(var, \"user.signingkey\")) {\n-\t\tif (!value)\n-\t\t\treturn config_error_nonbool(var);\n-\t\tset_signingkey(value);\n-\t\treturn 0;\n-\t}\n-\n+\tint status = git_gpg_config(var, value, cb);\n+\tif (status)\n+\t\treturn status;\n \treturn git_default_config(var, value, cb);\n }\n \n@@ -463,7 +399,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \n \tif (keyid) {\n \t\tsign = 1;\n-\t\tset_signingkey(keyid);\n+\t\tset_signing_key(keyid);\n \t}\n \tif (sign)\n \t\tannotate = 1;\ndiff --git a/builtin/verify-tag.c b/builtin/verify-tag.c\nindex 3134766..28c2174 100644\n--- a/builtin/verify-tag.c\n+++ b/builtin/verify-tag.c\n@@ -11,6 +11,7 @@\n #include \"run-command.h\"\n #include <signal.h>\n #include \"parse-options.h\"\n+#include \"gpg-interface.h\"\n \n static const char * const verify_tag_usage[] = {\n \t\t\"git verify-tag [-v|--verbose] <tag>...\",\n@@ -19,42 +20,16 @@ static const char * const verify_tag_usage[] = {\n \n static int run_gpg_verify(const char *buf, unsigned long size, int verbose)\n {\n-\tstruct child_process gpg;\n-\tconst char *args_gpg[] = {\"gpg\", \"--verify\", \"FILE\", \"-\", NULL};\n-\tchar path[PATH_MAX];\n-\tsize_t len;\n-\tint fd, ret;\n+\tint len;\n \n-\tfd = git_mkstemp(path, PATH_MAX, \".git_vtag_tmpXXXXXX\");\n-\tif (fd < 0)\n-\t\treturn error(\"could not create temporary file '%s': %s\",\n-\t\t\t\t\t\tpath, strerror(errno));\n-\tif (write_in_full(fd, buf, size) < 0)\n-\t\treturn error(\"failed writing temporary file '%s': %s\",\n-\t\t\t\t\t\tpath, strerror(errno));\n-\tclose(fd);\n-\n-\t/* find the length without signature */\n \tlen = parse_signature(buf, size);\n \tif (verbose)\n \t\twrite_in_full(1, buf, len);\n \n-\tmemset(&gpg, 0, sizeof(gpg));\n-\tgpg.argv = args_gpg;\n-\tgpg.in = -1;\n-\targs_gpg[2] = path;\n-\tif (start_command(&gpg)) {\n-\t\tunlink(path);\n-\t\treturn error(\"could not run gpg.\");\n-\t}\n-\n-\twrite_in_full(gpg.in, buf, len);\n-\tclose(gpg.in);\n-\tret = finish_command(&gpg);\n+\tif (size == len)\n+\t\treturn error(\"no signature found\");\n \n-\tunlink_or_warn(path);\n-\n-\treturn ret;\n+\treturn verify_signed_buffer(buf, len, buf + len, size - len, NULL);\n }\n \n static int verify_tag(const char *name, int verbose)\ndiff --git a/gpg-interface.c b/gpg-interface.c\nnew file mode 100644\nindex 0000000..ff232c8\n--- /dev/null\n+++ b/gpg-interface.c\n@@ -0,0 +1,138 @@\n+#include \"cache.h\"\n+#include \"run-command.h\"\n+#include \"strbuf.h\"\n+#include \"gpg-interface.h\"\n+#include \"sigchain.h\"\n+\n+static char *configured_signing_key;\n+\n+void set_signing_key(const char *key)\n+{\n+\tfree(configured_signing_key);\n+\tconfigured_signing_key = xstrdup(key);\n+}\n+\n+int git_gpg_config(const char *var, const char *value, void *cb)\n+{\n+\tif (!strcmp(var, \"user.signingkey\")) {\n+\t\tif (!value)\n+\t\t\treturn config_error_nonbool(var);\n+\t\tset_signing_key(value);\n+\t}\n+\treturn 0;\n+}\n+\n+const char *get_signing_key(void)\n+{\n+\tif (configured_signing_key)\n+\t\treturn configured_signing_key;\n+\treturn git_committer_info(IDENT_ERROR_ON_NO_NAME|IDENT_NO_DATE);\n+}\n+\n+/*\n+ * Create a detached signature for the contents of \"buffer\" and append\n+ * it after \"signature\"; \"buffer\" and \"signature\" can be the same\n+ * strbuf instance, which would cause the detached signature appended\n+ * at the end.\n+ */\n+int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key)\n+{\n+\tstruct child_process gpg;\n+\tconst char *args[4];\n+\tssize_t len;\n+\tsize_t i, j, bottom;\n+\n+\tmemset(&gpg, 0, sizeof(gpg));\n+\tgpg.argv = args;\n+\tgpg.in = -1;\n+\tgpg.out = -1;\n+\targs[0] = \"gpg\";\n+\targs[1] = \"-bsau\";\n+\targs[2] = signing_key;\n+\targs[3] = NULL;\n+\n+\tif (start_command(&gpg))\n+\t\treturn error(_(\"could not run gpg.\"));\n+\n+\t/*\n+\t * When the username signingkey is bad, program could be terminated\n+\t * because gpg exits without reading and then write gets SIGPIPE.\n+\t */\n+\tsigchain_push(SIGPIPE, SIG_IGN);\n+\n+\tif (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) {\n+\t\tclose(gpg.in);\n+\t\tclose(gpg.out);\n+\t\tfinish_command(&gpg);\n+\t\treturn error(_(\"gpg did not accept the data\"));\n+\t}\n+\tclose(gpg.in);\n+\n+\tbottom = signature->len;\n+\tlen = strbuf_read(signature, gpg.out, 1024);\n+\tclose(gpg.out);\n+\n+\tsigchain_pop(SIGPIPE);\n+\n+\tif (finish_command(&gpg) || !len || len < 0)\n+\t\treturn error(_(\"gpg failed to sign the data\"));\n+\n+\t/* Strip CR from the line endings, in case we are on Windows. */\n+\tfor (i = j = bottom; i < signature->len; i++)\n+\t\tif (signature->buf[i] != '\\r') {\n+\t\t\tif (i != j)\n+\t\t\t\tsignature->buf[j] = signature->buf[i];\n+\t\t\tj++;\n+\t\t}\n+\tstrbuf_setlen(signature, j);\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Run \"gpg\" to see if the payload matches the detached signature.\n+ * gpg_output_to tells where the output from \"gpg\" should go:\n+ *   < 0: /dev/null\n+ *   = 0: standard error of the calling process\n+ *   > 0: the specified file descriptor\n+ */\n+int verify_signed_buffer(const char *payload, size_t payload_size,\n+\t\t\t const char *signature, size_t signature_size,\n+\t\t\t struct strbuf *gpg_output)\n+{\n+\tstruct child_process gpg;\n+\tconst char *args_gpg[] = {\"gpg\", \"--verify\", \"FILE\", \"-\", NULL};\n+\tchar path[PATH_MAX];\n+\tint fd, ret;\n+\n+\tfd = git_mkstemp(path, PATH_MAX, \".git_vtag_tmpXXXXXX\");\n+\tif (fd < 0)\n+\t\treturn error(\"could not create temporary file '%s': %s\",\n+\t\t\t     path, strerror(errno));\n+\tif (write_in_full(fd, signature, signature_size) < 0)\n+\t\treturn error(\"failed writing detached signature to '%s': %s\",\n+\t\t\t     path, strerror(errno));\n+\tclose(fd);\n+\n+\tmemset(&gpg, 0, sizeof(gpg));\n+\tgpg.argv = args_gpg;\n+\tgpg.in = -1;\n+\tif (gpg_output)\n+\t\tgpg.err = -1;\n+\targs_gpg[2] = path;\n+\tif (start_command(&gpg)) {\n+\t\tunlink(path);\n+\t\treturn error(\"could not run gpg.\");\n+\t}\n+\n+\twrite_in_full(gpg.in, payload, payload_size);\n+\tclose(gpg.in);\n+\n+\tif (gpg_output)\n+\t\tstrbuf_read(gpg_output, gpg.err, 0);\n+\tret = finish_command(&gpg);\n+\n+\tunlink_or_warn(path);\n+\n+\treturn ret;\n+}\ndiff --git a/gpg-interface.h b/gpg-interface.h\nnew file mode 100644\nindex 0000000..b9c3608\n--- /dev/null\n+++ b/gpg-interface.h\n@@ -0,0 +1,10 @@\n+#ifndef GPG_INTERFACE_H\n+#define GPG_INTERFACE_H\n+\n+extern int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key);\n+extern int verify_signed_buffer(const char *payload, size_t payload_size, const char *signature, size_t signature_size, struct strbuf *gpg_output);\n+extern int git_gpg_config(const char *, const char *, void *);\n+extern void set_signing_key(const char *);\n+extern const char *get_signing_key(void);\n+\n+#endif\ndiff --git a/tag.c b/tag.c\nindex 7d38cc0..3aa186d 100644\n--- a/tag.c\n+++ b/tag.c\n@@ -139,6 +139,11 @@ int parse_tag(struct tag *item)\n \treturn ret;\n }\n \n+/*\n+ * Look at a signed tag object, and return the offset where\n+ * the embedded detached signature begins, or the end of the\n+ * data when there is no such signature.\n+ */\n size_t parse_signature(const char *buf, unsigned long size)\n {\n \tchar *eol;\n-- \n1.7.7.498.g3f2e50\n"},{"id":"178060","messageId":"1319071023-31919-3-git-send-email-gitster@pobox.com","threadId":"28598","inReplyTo":"1319071023-31919-1-git-send-email-gitster@pobox.com","subject":"[PATCH v4 2/5] commit: teach --gpg-sign option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-10-20T00:37:00Z","receivedAt":"2011-10-20T00:37:00Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"And this uses the gpg-interface.[ch] to allow signing the commit, i.e.\n\n    $ git commit --gpg-sign -m foo\n    You need a passphrase to unlock the secret key for\n    user: \"Junio C Hamano <gitster@pobox.com>\"\n    4096-bit RSA key, ID 96AFE6CB, created 2011-10-03 (main key ID 713660A7)\n\n    [master 8457d13] foo\n     1 files changed, 1 insertions(+), 0 deletions(-)\n\nThe lines of GPG detached signature are placed in new header lines, after\nthe standard tree/parent/author/committer headers, instead of tucking the\nsignature block at the end of the commit log message text (similar to how\nsigned tag is done), for multiple reasons:\n\n - The signature won't clutter output from \"git log\" and friends if it is\n   in the extra header. If we place it at the end of the log message, we\n   would need to teach \"git log\" and friends to strip the signature block\n   with an option.\n\n - Teaching new versions of \"git log\" and \"gitk\" to optionally verify and\n   show signatures is cleaner if we structurally know where the signature\n   block is (instead of scanning in the commit log message).\n\n - The signature needs to be stripped upon various commit rewriting\n   operations, e.g. rebase, filter-branch, etc. They all already ignore\n   unknown headers, but if we place signature in the log message, all of\n   these tools (and third-party tools) also need to learn how a signature\n   block would look like.\n\n - When we added the optional encoding header, all the tools (both in tree\n   and third-party) that acts on the raw commit object should have been\n   fixed to ignore headers they do not understand, so it is not like that\n   new header would be more likely to break than extra text in the commit.\n\nA commit made with the above sample sequence would look like this:\n\n    $ git cat-file commit HEAD\n    tree 3cd71d90e3db4136e5260ab54599791c4f883b9d\n    parent b87755351a47b09cb27d6913e6e0e17e6254a4d4\n    author Junio C Hamano <gitster@pobox.com> 1317862251 -0700\n    committer Junio C Hamano <gitster@pobox.com> 1317862251 -0700\n    gpgsig -----BEGIN PGP SIGNATURE-----\n    gpgsig Version: GnuPG v1.4.10 (GNU/Linux)\n    gpgsig\n    gpgsig iQIcBAABAgAGBQJOjPtrAAoJELC16IaWr+bL4TMP/RSe2Y/jYnCkds9unO5JEnfG\n    gpgsig ...\n    gpgsig =dt98\n    gpgsig -----END PGP SIGNATURE-----\n\n    foo\n\nbut \"git log\" (unless you ask for it with --pretty=raw) output is not\ncluttered with the signature information.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/commit-tree.c |   24 +++++++++++++++++++++---\n builtin/commit.c      |   12 ++++++++++--\n builtin/merge.c       |   16 ++++++++++++++--\n commit.c              |   40 +++++++++++++++++++++++++++++++++++++++-\n commit.h              |    2 +-\n notes-cache.c         |    2 +-\n notes-merge.c         |    2 +-\n 7 files changed, 87 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/commit-tree.c b/builtin/commit-tree.c\nindex d083795..a17811f 100644\n--- a/builtin/commit-tree.c\n+++ b/builtin/commit-tree.c\n@@ -8,8 +8,9 @@\n #include \"tree.h\"\n #include \"builtin.h\"\n #include \"utf8.h\"\n+#include \"gpg-interface.h\"\n \n-static const char commit_tree_usage[] = \"git commit-tree <sha1> [(-p <sha1>)...] < changelog\";\n+static const char commit_tree_usage[] = \"git commit-tree [-S<signer>] <sha1> [(-p <sha1>)...] < changelog\";\n \n static void new_parent(struct commit *parent, struct commit_list **parents_p)\n {\n@@ -25,6 +26,14 @@ static void new_parent(struct commit *parent, struct commit_list **parents_p)\n \tcommit_list_insert(parent, parents_p);\n }\n \n+static int commit_tree_config(const char *var, const char *value, void *cb)\n+{\n+\tint status = git_gpg_config(var, value, NULL);\n+\tif (status)\n+\t\treturn status;\n+\treturn git_default_config(var, value, cb);\n+}\n+\n int cmd_commit_tree(int argc, const char **argv, const char *prefix)\n {\n \tint i;\n@@ -32,11 +41,19 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)\n \tunsigned char tree_sha1[20];\n \tunsigned char commit_sha1[20];\n \tstruct strbuf buffer = STRBUF_INIT;\n+\tconst char *sign_commit = NULL;\n \n-\tgit_config(git_default_config, NULL);\n+\tgit_config(commit_tree_config, NULL);\n \n \tif (argc < 2 || !strcmp(argv[1], \"-h\"))\n \t\tusage(commit_tree_usage);\n+\n+\tif (!memcmp(argv[1], \"-S\", 2)) {\n+\t\tsign_commit = argv[1] + 2;\n+\t\targv++;\n+\t\targc--;\n+\t}\n+\n \tif (get_sha1(argv[1], tree_sha1))\n \t\tdie(\"Not a valid object name %s\", argv[1]);\n \n@@ -56,7 +73,8 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)\n \tif (strbuf_read(&buffer, 0, 0) < 0)\n \t\tdie_errno(\"git commit-tree: failed to read\");\n \n-\tif (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) {\n+\tif (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1,\n+\t\t\tNULL, sign_commit)) {\n \t\tstrbuf_release(&buffer);\n \t\treturn 1;\n \t}\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex cbc9613..90cf7e8 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -26,6 +26,7 @@\n #include \"unpack-trees.h\"\n #include \"quote.h\"\n #include \"submodule.h\"\n+#include \"gpg-interface.h\"\n \n static const char * const builtin_commit_usage[] = {\n \t\"git commit [options] [--] <filepattern>...\",\n@@ -85,6 +86,8 @@ static int all, edit_flag, also, interactive, patch_interactive, only, amend, si\n static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;\n static int no_post_rewrite, allow_empty_message;\n static char *untracked_files_arg, *force_date, *ignore_submodule_arg;\n+static char *sign_commit;\n+\n /*\n  * The default commit message cleanup mode will remove the lines\n  * beginning with # (shell comments) and leading and trailing\n@@ -144,6 +147,8 @@ static struct option builtin_commit_options[] = {\n \tOPT_BOOLEAN('e', \"edit\", &edit_flag, \"force edit of commit\"),\n \tOPT_STRING(0, \"cleanup\", &cleanup_arg, \"default\", \"how to strip spaces and #comments from message\"),\n \tOPT_BOOLEAN(0, \"status\", &include_status, \"include status in commit message template\"),\n+\t{ OPTION_STRING, 'S', \"gpg-sign\", &sign_commit, \"key id\",\n+\t  \"GPG sign commit\", PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n \t/* end commit message options */\n \n \tOPT_GROUP(\"Commit contents options\"),\n@@ -1323,6 +1328,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1,\n static int git_commit_config(const char *k, const char *v, void *cb)\n {\n \tstruct wt_status *s = cb;\n+\tint status;\n \n \tif (!strcmp(k, \"commit.template\"))\n \t\treturn git_config_pathname(&template_file, k, v);\n@@ -1330,7 +1336,9 @@ static int git_commit_config(const char *k, const char *v, void *cb)\n \t\tinclude_status = git_config_bool(k, v);\n \t\treturn 0;\n \t}\n-\n+\tstatus = git_gpg_config(k, v, NULL);\n+\tif (status)\n+\t\treturn status;\n \treturn git_status_config(k, v, s);\n }\n \n@@ -1481,7 +1489,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t}\n \n \tif (commit_tree(sb.buf, active_cache_tree->sha1, parents, sha1,\n-\t\t\tauthor_ident.buf)) {\n+\t\t\tauthor_ident.buf, sign_commit)) {\n \t\trollback_index_files();\n \t\tdie(_(\"failed to write commit object\"));\n \t}\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex ab4077f..53cff02 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -26,6 +26,7 @@\n #include \"merge-recursive.h\"\n #include \"resolve-undo.h\"\n #include \"remote.h\"\n+#include \"gpg-interface.h\"\n \n #define DEFAULT_TWOHEAD (1<<0)\n #define DEFAULT_OCTOPUS (1<<1)\n@@ -63,6 +64,7 @@ static int allow_rerere_auto;\n static int abort_current_merge;\n static int show_progress = -1;\n static int default_to_upstream;\n+static const char *sign_commit;\n \n static struct strategy all_strategy[] = {\n \t{ \"recursive\",  DEFAULT_TWOHEAD | NO_TRIVIAL },\n@@ -206,6 +208,8 @@ static struct option builtin_merge_options[] = {\n \tOPT_BOOLEAN(0, \"abort\", &abort_current_merge,\n \t\t\"abort the current in-progress merge\"),\n \tOPT_SET_INT(0, \"progress\", &show_progress, \"force progress reporting\", 1),\n+\t{ OPTION_STRING, 'S', \"gpg-sign\", &sign_commit, \"key id\",\n+\t  \"GPG sign commit\", PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n \tOPT_END()\n };\n \n@@ -525,6 +529,8 @@ static void parse_branch_merge_options(char *bmo)\n \n static int git_merge_config(const char *k, const char *v, void *cb)\n {\n+\tint status;\n+\n \tif (branch && !prefixcmp(k, \"branch.\") &&\n \t\t!prefixcmp(k + 7, branch) &&\n \t\t!strcmp(k + 7 + strlen(branch), \".mergeoptions\")) {\n@@ -562,6 +568,10 @@ static int git_merge_config(const char *k, const char *v, void *cb)\n \t\tdefault_to_upstream = git_config_bool(k, v);\n \t\treturn 0;\n \t}\n+\n+\tstatus = git_gpg_config(k, v, NULL);\n+\tif (status)\n+\t\treturn status;\n \treturn git_diff_ui_config(k, v, cb);\n }\n \n@@ -870,7 +880,8 @@ static int merge_trivial(void)\n \tparent->next->item = remoteheads->item;\n \tparent->next->next = NULL;\n \trun_prepare_commit_msg();\n-\tcommit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);\n+\tcommit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL,\n+\t\t    sign_commit);\n \tfinish(result_commit, \"In-index merge\");\n \tdrop_save();\n \treturn 0;\n@@ -900,7 +911,8 @@ static int finish_automerge(struct commit_list *common,\n \tfree_commit_list(remoteheads);\n \tstrbuf_addch(&merge_msg, '\\n');\n \trun_prepare_commit_msg();\n-\tcommit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);\n+\tcommit_tree(merge_msg.buf, result_tree, parents, result_commit,\n+\t\t    NULL, sign_commit);\n \tstrbuf_addf(&buf, \"Merge made by the '%s' strategy.\", wt_strategy);\n \tfinish(result_commit, buf.buf);\n \tstrbuf_release(&buf);\ndiff --git a/commit.c b/commit.c\nindex 97b4327..4bff3cd 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -6,6 +6,7 @@\n #include \"diff.h\"\n #include \"revision.h\"\n #include \"notes.h\"\n+#include \"gpg-interface.h\"\n \n int save_commit_buffer = 1;\n \n@@ -814,6 +815,40 @@ struct commit_list *reduce_heads(struct commit_list *heads)\n \treturn result;\n }\n \n+static const char gpg_sig_header[] = \"gpgsig \";\n+static const int gpg_sig_header_len = sizeof(gpg_sig_header) - 1;\n+\n+static int do_sign_commit(struct strbuf *buf, const char *keyid)\n+{\n+\tstruct strbuf sig = STRBUF_INIT;\n+\tint inspos, copypos;\n+\n+\t/* find the end of the header */\n+\tinspos = strstr(buf->buf, \"\\n\\n\") - buf->buf + 1;\n+\n+\tif (!keyid || !*keyid)\n+\t\tkeyid = get_signing_key();\n+\tif (sign_buffer(buf, &sig, keyid)) {\n+\t\tstrbuf_release(&sig);\n+\t\treturn -1;\n+\t}\n+\n+\tfor (copypos = 0; sig.buf[copypos]; ) {\n+\t\tconst char *bol = sig.buf + copypos;\n+\t\tconst char *eol = strchrnul(bol, '\\n');\n+\t\tint len = (eol - bol) + !!*eol;\n+\n+\t\tstrbuf_insert(buf, inspos, gpg_sig_header, gpg_sig_header_len);\n+\t\tinspos += gpg_sig_header_len;\n+\t\tstrbuf_insert(buf, inspos, bol, len);\n+\t\tinspos += len;\n+\t\tcopypos += len;\n+\t}\n+\tstrbuf_release(&sig);\n+\treturn 0;\n+}\n+\n+\n static const char commit_utf8_warn[] =\n \"Warning: commit message does not conform to UTF-8.\\n\"\n \"You may want to amend it after fixing the message, or set the config\\n\"\n@@ -821,7 +856,7 @@ static const char commit_utf8_warn[] =\n \n int commit_tree(const char *msg, unsigned char *tree,\n \t\tstruct commit_list *parents, unsigned char *ret,\n-\t\tconst char *author)\n+\t\tconst char *author, const char *sign_commit)\n {\n \tint result;\n \tint encoding_is_utf8;\n@@ -864,6 +899,9 @@ int commit_tree(const char *msg, unsigned char *tree,\n \tif (encoding_is_utf8 && !is_utf8(buffer.buf))\n \t\tfprintf(stderr, commit_utf8_warn);\n \n+\tif (sign_commit && do_sign_commit(&buffer, sign_commit))\n+\t\treturn -1;\n+\n \tresult = write_sha1_file(buffer.buf, buffer.len, commit_type, ret);\n \tstrbuf_release(&buffer);\n \treturn result;\ndiff --git a/commit.h b/commit.h\nindex 12d100b8..8c2419b 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -175,6 +175,6 @@ struct commit_list *reduce_heads(struct commit_list *heads);\n \n extern int commit_tree(const char *msg, unsigned char *tree,\n \t\tstruct commit_list *parents, unsigned char *ret,\n-\t\tconst char *author);\n+\t\t       const char *author, const char *sign_commit);\n \n #endif /* COMMIT_H */\ndiff --git a/notes-cache.c b/notes-cache.c\nindex 4c8984e..c36a960 100644\n--- a/notes-cache.c\n+++ b/notes-cache.c\n@@ -56,7 +56,7 @@ int notes_cache_write(struct notes_cache *c)\n \n \tif (write_notes_tree(&c->tree, tree_sha1))\n \t\treturn -1;\n-\tif (commit_tree(c->validity, tree_sha1, NULL, commit_sha1, NULL) < 0)\n+\tif (commit_tree(c->validity, tree_sha1, NULL, commit_sha1, NULL, NULL) < 0)\n \t\treturn -1;\n \tif (update_ref(\"update notes cache\", c->tree.ref, commit_sha1, NULL,\n \t\t       0, QUIET_ON_ERR) < 0)\ndiff --git a/notes-merge.c b/notes-merge.c\nindex e1aaf43..c29c434 100644\n--- a/notes-merge.c\n+++ b/notes-merge.c\n@@ -546,7 +546,7 @@ void create_notes_commit(struct notes_tree *t, struct commit_list *parents,\n \t\t/* else: t->ref points to nothing, assume root/orphan commit */\n \t}\n \n-\tif (commit_tree(msg, tree_sha1, parents, result_sha1, NULL))\n+\tif (commit_tree(msg, tree_sha1, parents, result_sha1, NULL, NULL))\n \t\tdie(\"Failed to commit notes tree to database\");\n }\n \n-- \n1.7.7.498.g3f2e50\n"},{"id":"178059","messageId":"1319071023-31919-4-git-send-email-gitster@pobox.com","threadId":"28598","inReplyTo":"1319071023-31919-1-git-send-email-gitster@pobox.com","subject":"[PATCH v4 3/5] log: --show-signature","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-10-20T00:37:01Z","receivedAt":"2011-10-20T00:37:01Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This teaches the \"log\" family of commands to pass the GPG signature in the\ncommit objects to \"gpg --verify\" via the verify_signed_buffer() interface\nused to verify signed tag objects. E.g.\n\n    $ git show --show-signature -s HEAD\n\nshows GPG output in the header part of the output.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n commit.c   |   34 ++++++++++++++++++++++++++++++++++\n commit.h   |    3 +++\n log-tree.c |   39 +++++++++++++++++++++++++++++++++++++++\n revision.c |    2 ++\n revision.h |    1 +\n 5 files changed, 79 insertions(+), 0 deletions(-)\n\ndiff --git a/commit.c b/commit.c\nindex 4bff3cd..93045a2 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -848,6 +848,40 @@ static int do_sign_commit(struct strbuf *buf, const char *keyid)\n \treturn 0;\n }\n \n+int parse_signed_commit(const unsigned char *sha1,\n+\t\t\tstruct strbuf *payload, struct strbuf *signature)\n+{\n+\tunsigned long size;\n+\tenum object_type type;\n+\tchar *buffer = read_sha1_file(sha1, &type, &size);\n+\tint in_header, saw_signature = -1;\n+\tchar *line;\n+\n+\tif (!buffer || type != OBJ_COMMIT)\n+\t\tgoto cleanup;\n+\n+\tline = buffer;\n+\tin_header = 1;\n+\tsaw_signature = 0;\n+\twhile (*line) {\n+\t\tchar *next = strchrnul(line, '\\n');\n+\t\tif (*next)\n+\t\t\tnext++;\n+\t\tif (in_header && !prefixcmp(line, gpg_sig_header)) {\n+\t\t\tconst char *sig = line + gpg_sig_header_len;\n+\t\t\tstrbuf_add(signature, sig, next - sig);\n+\t\t\tsaw_signature = 1;\n+\t\t} else {\n+\t\t\tstrbuf_add(payload, line, next - line);\n+\t\t}\n+\t\tif (*line == '\\n')\n+\t\t\tin_header = 0;\n+\t\tline = next;\n+\t}\n+ cleanup:\n+\tfree(buffer);\n+\treturn saw_signature;\n+}\n \n static const char commit_utf8_warn[] =\n \"Warning: commit message does not conform to UTF-8.\\n\"\ndiff --git a/commit.h b/commit.h\nindex 8c2419b..1885471 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -177,4 +177,7 @@ extern int commit_tree(const char *msg, unsigned char *tree,\n \t\tstruct commit_list *parents, unsigned char *ret,\n \t\t       const char *author, const char *sign_commit);\n \n+extern int parse_signed_commit(const unsigned char *sha1,\n+\t\t\t       struct strbuf *message, struct strbuf *signature);\n+\n #endif /* COMMIT_H */\ndiff --git a/log-tree.c b/log-tree.c\nindex 24c295e..f7b6976 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -8,6 +8,7 @@\n #include \"refs.h\"\n #include \"string-list.h\"\n #include \"color.h\"\n+#include \"gpg-interface.h\"\n \n struct decoration name_decoration = { \"object names\" };\n \n@@ -395,6 +396,41 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit,\n \t*extra_headers_p = extra_headers;\n }\n \n+static void show_signature(struct rev_info *opt, struct commit *commit)\n+{\n+\tstruct strbuf payload = STRBUF_INIT;\n+\tstruct strbuf signature = STRBUF_INIT;\n+\tstruct strbuf gpg_output = STRBUF_INIT;\n+\tint status;\n+\tconst char *color, *reset, *bol, *eol;\n+\n+\tif (parse_signed_commit(commit->object.sha1, &payload, &signature) <= 0)\n+\t\tgoto out;\n+\n+\tstatus = verify_signed_buffer(payload.buf, payload.len,\n+\t\t\t\t      signature.buf, signature.len,\n+\t\t\t\t      &gpg_output);\n+\tif (status && !gpg_output.len)\n+\t\tstrbuf_addstr(&gpg_output, \"No signature\\n\");\n+\n+\tcolor = diff_get_color_opt(&opt->diffopt,\n+\t\t\t\t   status ? DIFF_WHITESPACE : DIFF_FRAGINFO);\n+\treset = diff_get_color_opt(&opt->diffopt, DIFF_RESET);\n+\n+\tbol = gpg_output.buf;\n+\twhile (*bol) {\n+\t\teol = strchrnul(bol, '\\n');\n+\t\tprintf(\"%s%.*s%s%s\", color, (int)(eol - bol), bol, reset,\n+\t\t       *eol ? \"\\n\" : \"\");\n+\t\tbol = (*eol) ? (eol + 1) : eol;\n+\t}\n+\n+ out:\n+\tstrbuf_release(&gpg_output);\n+\tstrbuf_release(&payload);\n+\tstrbuf_release(&signature);\n+}\n+\n void show_log(struct rev_info *opt)\n {\n \tstruct strbuf msgbuf = STRBUF_INIT;\n@@ -502,6 +538,9 @@ void show_log(struct rev_info *opt)\n \t\t}\n \t}\n \n+\tif (opt->show_signature)\n+\t\tshow_signature(opt, commit);\n+\n \tif (!commit->buffer)\n \t\treturn;\n \ndiff --git a/revision.c b/revision.c\nindex c46cfaa..860a312 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1381,6 +1381,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg\n \t\trevs->show_notes = 1;\n \t\trevs->show_notes_given = 1;\n \t\trevs->notes_opt.use_default_notes = 1;\n+\t} else if (!strcmp(arg, \"--show-signature\")) {\n+\t\trevs->show_signature = 1;\n \t} else if (!prefixcmp(arg, \"--show-notes=\") ||\n \t\t   !prefixcmp(arg, \"--notes=\")) {\n \t\tstruct strbuf buf = STRBUF_INIT;\ndiff --git a/revision.h b/revision.h\nindex 3d64ada..198bb95 100644\n--- a/revision.h\n+++ b/revision.h\n@@ -89,6 +89,7 @@ struct rev_info {\n \t\t\tshow_merge:1,\n \t\t\tshow_notes:1,\n \t\t\tshow_notes_given:1,\n+\t\t\tshow_signature:1,\n \t\t\tpretty_given:1,\n \t\t\tabbrev_commit:1,\n \t\t\tabbrev_commit_given:1,\n-- \n1.7.7.498.g3f2e50\n"},{"id":"178058","messageId":"1319071023-31919-5-git-send-email-gitster@pobox.com","threadId":"28598","inReplyTo":"1319071023-31919-1-git-send-email-gitster@pobox.com","subject":"[PATCH v4 4/5] t7004: extract generic \"GPG testing\" bits","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-10-20T00:37:02Z","receivedAt":"2011-10-20T00:37:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Signed-off-by: Junio C Hamano <gitster@pobox.com>\n---\n t/lib-gpg.sh          |   29 +++++++++++++++++++++++++++++\n t/lib-gpg/pubring.gpg |  Bin 0 -> 1164 bytes\n t/lib-gpg/random_seed |  Bin 0 -> 600 bytes\n t/lib-gpg/secring.gpg |  Bin 0 -> 1237 bytes\n t/lib-gpg/trustdb.gpg |  Bin 0 -> 1280 bytes\n t/t7004-tag.sh        |   29 +----------------------------\n t/t7004/pubring.gpg   |  Bin 1164 -> 0 bytes\n t/t7004/random_seed   |  Bin 600 -> 0 bytes\n t/t7004/secring.gpg   |  Bin 1237 -> 0 bytes\n t/t7004/trustdb.gpg   |  Bin 1280 -> 0 bytes\n 10 files changed, 30 insertions(+), 28 deletions(-)\n create mode 100644 t/lib-gpg.sh\n create mode 100644 t/lib-gpg/pubring.gpg\n create mode 100644 t/lib-gpg/random_seed\n create mode 100644 t/lib-gpg/secring.gpg\n create mode 100644 t/lib-gpg/trustdb.gpg\n delete mode 100644 t/t7004/pubring.gpg\n delete mode 100644 t/t7004/random_seed\n delete mode 100644 t/t7004/secring.gpg\n delete mode 100644 t/t7004/trustdb.gpg\n\ndiff --git a/t/lib-gpg.sh b/t/lib-gpg.sh\nnew file mode 100644\nindex 0000000..eb09027\n--- /dev/null\n+++ b/t/lib-gpg.sh\n@@ -0,0 +1,29 @@\n+#!/bin/sh\n+\n+# Check if gpg is available\n+gpg --version >/dev/null 2>/dev/null\n+if [ $? -eq 127 ]; then\n+\tsay \"# gpg not found - skipping tag signing and verification tests\"\n+else\n+\t# As said here: http://www.gnupg.org/documentation/faqs.html#q6.19\n+\t# the gpg version 1.0.6 didn't parse trust packets correctly, so for\n+\t# that version, creation of signed tags using the generated key fails.\n+\tcase \"$(gpg --version)\" in\n+\t'gpg (GnuPG) 1.0.6'*)\n+\t\tsay \"Skipping signed tag tests, because a bug in 1.0.6 version\"\n+\t\t;;\n+\t*)\n+\t\ttest_set_prereq GPG\n+\t\t;;\n+\tesac\n+fi\n+\n+# key generation info: gpg --homedir t/t7004 --gen-key\n+# Type DSA and Elgamal, size 2048 bits, no expiration date.\n+# Name and email: C O Mitter <committer@example.com>\n+# No password given, to enable non-interactive operation.\n+\n+cp -R \"$TEST_DIRECTORY\"/lib-gpg ./gpghome\n+chmod 0700 gpghome\n+GNUPGHOME=\"$(pwd)/gpghome\"\n+export GNUPGHOME\ndiff --git a/t/lib-gpg/pubring.gpg b/t/lib-gpg/pubring.gpg\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..83855fa4e1c6c37afe550c17afa1e7971042ded5\nGIT binary patch\nliteral 1164\nzcmV;71ateD0ipy(XUi!O1OT%L_{gO;4KKDfwF;#WWYZ2?*>Ph_j9y>uobJf#jXEbw\nzQUOEXs+@;X*)1kyN*7VwW}f-GD3>Vlu2F6j{T^Kt%(kMo(W3C}W^X)NL|0VdGIzc`\nz5@TY!;Du~AFGW=#0vYgS@U2j8Q{Qe9F~lQEBVt+(yjM1jQ{YLe0lxsC<HX~BiFSU3\nzs3oq~QtxsAzbwuV1N{{&PcudMk6}p><?O}^6vikhMm+>LD}W25r?wdKr$(|R8_;Mn\nz&Yh%2Hbu9IX>#(R8S&@jVI!K^K7AI$=A+*-qJ>-J^fdR_m5GZao*%P{nee=O$Eb$s\nzes#O%4Z_4OAZW}8Ey8%%w->rA>z!nf)&NPikkr&pkRlB>=W+x68fwevWl9Ys2R3m2\nzNPf<ig>x1y^AXyemu>Q}DAp%==Q`5sCrBzLaH^rOm1NLtt-ig*IfY?HMdv66@%u%#\nzy0K62;Z+4O3Ol6i*<7GlXeW6FC?0yV!c_#yGozNjrOE#8+Rw@Z8cGAG=eE%uMUW=V\nzX(76db;AG@lD)hAv?4<wPasWcbaZ8MAUtDlZEb0EbY*ftWq4t2aBO8RV{dIfh+YH}\nz5dr`n1p-EA%P9gI0|pBT2nPcK0~G=T0~P`S0v-VZ7k~f?2@n&u^&ZXMLk)Bt0G~<e\nziCMX)QRqT4mEiivJ-X>{Mxg+pzucC_!d-8(MV+3G?_=VDC^nQ}umS)BxdIIYMrX@T\nz5C{Oyxp_ZR>*yfS@@M%BVY6{X4EXUYpbiBs(uw7knRB|b)>)=o+wQ%-v34o&a|fQ;\nz&R=xVK8Z-jS8R4m_gj2C$xPq$xv1H553!y9ip8A~vxoyq&fFbw!L$zU@hDh#f$r>>\nzAYPcTf%K%xv5<+JivV@Da!&`-v8eIo5ApKIdiK7E`krA1X!I73&)t#yo}GERUSnBN\nzlK-+<I<**0!u}jvbPz5b{hhe8_PVcR!i8ObFp-W0bqNWKA+W14zV9Mj$a~(KZ-0uf\nz|BVvaf!K}k92MNalP4hQ^Yxqk-*SzY6q{6Cw*+Fr_+LZp=BFzuo3pVX*T?Ic-|hbZ\nz1PurPv|=$EKX~Bz_Bz;JW71^>8;AnsY?5ub{hajO)@PHTt`On@Z;}jyeue<@oS$3l\nzEu9m5O={<O&85HqUN|R$bZiPXX4YsWsPOn@@M)Lf;hyI_(eM$*#Sf3}T=1jzGvsiy\nziKuCmV^)`UC@8ma=jBiHqr!1L4>w`5P=-vvIiJTGj0-lRpc+EU9(`B#rn1m1WqmXr\nz5Z+N|DR*y*k<4fY0I_eP7qVo9Fx58WMwaK^N~I=>1VN29R|Umg^2q$WGlE#9AFC2_\nzOYG5M$}OL?FQ)ej0!KGwY-hAFz#wAPr>3&{`s7!HUZ22Y7KY{9ly1wQ8^hqBojPv8\nzh)Dz(5dr`S1p-EA%T59t3;+rV5EHib9?jlE4Z-XHpqd%~B34SUmtRi+{;vv2ei?p%\neLjau^#pu5ba=n|GS94<zkje^zT1YFf0ssTLm?$>@\n\nliteral 0\nHcmV?d00001\n\ndiff --git a/t/lib-gpg/random_seed b/t/lib-gpg/random_seed\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..8fed1339ed0a744e5663f4a5e6b6ac9bae3d8524\nGIT binary patch\nliteral 600\nzcmV-e0;m1=h9nBV>1C6QsKJEiEJaD@Q3F8s5u<$E+<2(By)JAZSxviTsXg(wKC+O%\nzzvV{Z>W3*k?r7~pgmmkbw8-x{Am!eeN)z?cwIHcT2jqgiA(SXo<iO=E?cY80`p#w8\nz)O-&?SnwsJ=1VJ-?26&*g88Nr8E=g2onRW^(c+2nJlX)?dmK)tPO0EY-!B!vMCv1)\nz-AOW(3WuF+7IdSxMnzrDgnMqVU=|+YFxlY|VeR+Fg<%C@0Xupi0<S7QYJyFTR$}FQ\nzzoSAbU>CoCKWKX;!3@L_U=aFUm!M<>ILG}$`bfnadAkLQbI-upV7Qwf^OE&N45Pz<\nzk~^KlzNC6)d@QGv=K5-At&A8FS&MQSR`LB}@R1?A3K1p(vM>7CK}EfFhmBJd&cH^-\nz(3Ih^`VuoVBB|w~p!Q^#DY%V2A2FhXu<Bp*L)lSCUdqRyI5wxMG&E1sL$)E$Zo&pJ\nzgy#;fENqHImgN>LL2!7DhfZ}&;BSAyz=T0#S?2+NET5St@16L?YI?5Io%<uD|2}hl\nzx0xsuefz1+bM^-ZIgtKs=)&VAI8(MfytvM>t>%~nsXUb~*EkptHiN?W{=DRu_s;2u\nziHh{2&>;CQO7;>{$DN33_Ef}g+;b<2hIF^p(Y>^riLBb*Y2Xw>F8)jp49&oLKJOic\nz+V{Lt!_`eKGhyk5Edie{-^#n!TFlsfux*QBRZEh^4SVePPmb{BvF|>sKd2cYg@vKp\nmVI8jcB1(k(tlt^Kr<{EMs>|b*d70nyVMQcc%xEnE(#Uq3d^-35\n\nliteral 0\nHcmV?d00001\n\ndiff --git a/t/lib-gpg/secring.gpg b/t/lib-gpg/secring.gpg\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..d831cd9eb3eee613d3c0e1a71093ae01ea7347e3\nGIT binary patch\nliteral 1237\nzcmV;`1S<QL0lNf7XUi!O1OT%L_{gO;4KKDfwF;#WWYZ2?*>Ph_j9y>uobJf#jXEbw\nzQUOEXs+@;X*)1kyN*7VwW}f-GD3>Vlu2F6j{T^Kt%(kMo(W3C}W^X)NL|0VdGIzc`\nz5@TY!;Du~AFGW=#0vYgS@U2j8Q{Qe9F~lQEBVt+(yjM1jQ{YLe0lxsC<HX~BiFSU3\nzs3oq~QtxsAzbwuV1N{{&PcudMk6}p><?O}^6vikhMm+>LD}W25r?wdKr$(|R8_;Mn\nz&Yh%2Hbu9IX>#(R8S&@jVI!K^K7AI$=A+*-qJ>-J^fdR_m5GZao*%P{nee=O$Eb$s\nzes#O%4Z_4OAZW}8Ey8%%w->rA>z!nf)&NPikkr&pkRlB>=W+x68fwevWl9Ys2R3m2\nzNPf<ig>x1y^AXyemu>Q}DAp%==Q`5sCrBzLaH^rOm1NLtt-ig*IfY?HMdv66@%u%#\nzy0K62;Z+4O3Ol6i*<7GlXeW6FC?0yV!c_#yGozNjrOE#8+Rw@Z8cGAG=eE%uMUW=V\nzX(76db;AG@lD)hA005tK#LT31ryNoF9o-(`X`Xl5w88@mle8j3AWtAoX>@dDav(fo\nzZ*6U9baZ8MKxKGgZE$R5E@N+PK8Rif6A=Oc9t8qMXUi!98v_Ol2?z%R0s|ES0|OQU\nz0RkQY0vCV)3JDMsw)Gy(-a`#^9RQ%c+5fOQwB&AM#DD9)s1L@!pm9S0pF>@|C2*qL\nz%1j5#NZ6^U*PB>^d9VTi0G$Fo1V(4eP7nwH&bfI%Q|ss;(eh{c3}LfzMGW}yE1(Vq\nzEz*hQmYH+9vesFqTifovzOi;G@N);A+0I{d(LRYt$5(82OZQuRJjqPo^tq_na}Tkd\nz|BA((5wnN`O3vIJaly0>?(ryCcY*Hgm>^!5uz~cX%CV4%oQnW;wsKDg)3K=W<qz@l\nz$a?m^i29yk259sakI&ta{GOe8x?W>hQIh|%Svs{CO~U>hTXYaE9sQlSv-Y~LWWt4A\nze=w1b1$7Atj3KbAF~092T*!Ogn{R)Lum6n_*@4)N@EjG~z>_B+>GSoQ{oitpmlT^+\nzUAF{c!T4W8?B=H{DVwvgAlJw1n&0jJ00a#P0JLH;8$Wp9`Sv>4USrZ_1sjM0<!q8|\nzxc!{;-PUK5pso<&0dJBFgMNkp@|>Sr>@A%Wd`)WSc+I820A4sJf^=*OHfGjnC8+TD\nzW$<a2;o+X=JkjtG#>Eeh?p*Mr_A}&gvx%r_lw($xcPJ>gap&bv^P|FXJr6fwvQUOh\nzz&W4C8jK4zqM#Z=%N~7K_NKDXEM<K(9uVG9XDN4YijmA{1^}^dq8GAcg)r4N<3^U}\nz-b$q=iUdK8Hdh73UGm8MyfcDWr5~#ja!c&dVahF^v@fRj3IazrWNc@&F~A^V)u*Pi\nz`ugNogkGP(V-|+x+mvq0pc}*Bpq)By!2khN4M6SOU21NKpXW5P=UU_LoDzvrTdR;4\nzU)W&&o9V6>MQOk{A%?1(Ozs{Qqlifa7!d*h2?YX1XUk3k8w>yn2@n&u^&ZXMLk+>~\nz0HDxvfGrJTMY@$ys#k#t?D63Zq=W#R3eUeT(L61`VGHe?zP;`rnta#-umS)8nA%Rq\n\nliteral 0\nHcmV?d00001\n\ndiff --git a/t/lib-gpg/trustdb.gpg b/t/lib-gpg/trustdb.gpg\nnew file mode 100644\nindex 0000000000000000000000000000000000000000..abace962b8bf84be688a6f27e4ebd0ee7052f210\nGIT binary patch\nliteral 1280\nzcmZQfFGy!*W@Ke#U|?`dKkWykumMIcY@%4iM%7^n6rj+M4;MLzzlOX&pwTnxkD-}P\nzc^HbXN0fL!SIq1?>env3?W^3`d(OOU5YNaX{KU(k^<0;M@87ONv)_6ZxD={-=<kYO\nM2Ud3=2BC}r0AuhNr2qf`\n\nliteral 0\nHcmV?d00001\n\ndiff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\nindex 097ce2b..ded5c86 100755\n--- a/t/t7004-tag.sh\n+++ b/t/t7004-tag.sh\n@@ -8,6 +8,7 @@ test_description='git tag\n Tests for operations with tags.'\n \n . ./test-lib.sh\n+. \"$TEST_DIRECTORY/lib-gpg.sh\"\n \n # creating and listing lightweight tags:\n \n@@ -585,24 +586,6 @@ test_expect_success \\\n \ttest_cmp expect actual\n '\n \n-# subsequent tests require gpg; check if it is available\n-gpg --version >/dev/null 2>/dev/null\n-if [ $? -eq 127 ]; then\n-\tsay \"# gpg not found - skipping tag signing and verification tests\"\n-else\n-\t# As said here: http://www.gnupg.org/documentation/faqs.html#q6.19\n-\t# the gpg version 1.0.6 didn't parse trust packets correctly, so for\n-\t# that version, creation of signed tags using the generated key fails.\n-\tcase \"$(gpg --version)\" in\n-\t'gpg (GnuPG) 1.0.6'*)\n-\t\tsay \"Skipping signed tag tests, because a bug in 1.0.6 version\"\n-\t\t;;\n-\t*)\n-\t\ttest_set_prereq GPG\n-\t\t;;\n-\tesac\n-fi\n-\n # trying to verify annotated non-signed tags:\n \n test_expect_success GPG \\\n@@ -625,16 +608,6 @@ test_expect_success GPG \\\n \n # creating and verifying signed tags:\n \n-# key generation info: gpg --homedir t/t7004 --gen-key\n-# Type DSA and Elgamal, size 2048 bits, no expiration date.\n-# Name and email: C O Mitter <committer@example.com>\n-# No password given, to enable non-interactive operation.\n-\n-cp -R \"$TEST_DIRECTORY\"/t7004 ./gpghome\n-chmod 0700 gpghome\n-GNUPGHOME=\"$(pwd)/gpghome\"\n-export GNUPGHOME\n-\n get_tag_header signed-tag $commit commit $time >expect\n echo 'A signed tag message' >>expect\n echo '-----BEGIN PGP SIGNATURE-----' >>expect\ndiff --git a/t/t7004/pubring.gpg b/t/t7004/pubring.gpg\ndeleted file mode 100644\nindex 83855fa4e1c6c37afe550c17afa1e7971042ded5..0000000000000000000000000000000000000000\nGIT binary patch\nliteral 0\nHcmV?d00001\n\nliteral 1164\nzcmV;71ateD0ipy(XUi!O1OT%L_{gO;4KKDfwF;#WWYZ2?*>Ph_j9y>uobJf#jXEbw\nzQUOEXs+@;X*)1kyN*7VwW}f-GD3>Vlu2F6j{T^Kt%(kMo(W3C}W^X)NL|0VdGIzc`\nz5@TY!;Du~AFGW=#0vYgS@U2j8Q{Qe9F~lQEBVt+(yjM1jQ{YLe0lxsC<HX~BiFSU3\nzs3oq~QtxsAzbwuV1N{{&PcudMk6}p><?O}^6vikhMm+>LD}W25r?wdKr$(|R8_;Mn\nz&Yh%2Hbu9IX>#(R8S&@jVI!K^K7AI$=A+*-qJ>-J^fdR_m5GZao*%P{nee=O$Eb$s\nzes#O%4Z_4OAZW}8Ey8%%w->rA>z!nf)&NPikkr&pkRlB>=W+x68fwevWl9Ys2R3m2\nzNPf<ig>x1y^AXyemu>Q}DAp%==Q`5sCrBzLaH^rOm1NLtt-ig*IfY?HMdv66@%u%#\nzy0K62;Z+4O3Ol6i*<7GlXeW6FC?0yV!c_#yGozNjrOE#8+Rw@Z8cGAG=eE%uMUW=V\nzX(76db;AG@lD)hAv?4<wPasWcbaZ8MAUtDlZEb0EbY*ftWq4t2aBO8RV{dIfh+YH}\nz5dr`n1p-EA%P9gI0|pBT2nPcK0~G=T0~P`S0v-VZ7k~f?2@n&u^&ZXMLk)Bt0G~<e\nziCMX)QRqT4mEiivJ-X>{Mxg+pzucC_!d-8(MV+3G?_=VDC^nQ}umS)BxdIIYMrX@T\nz5C{Oyxp_ZR>*yfS@@M%BVY6{X4EXUYpbiBs(uw7knRB|b)>)=o+wQ%-v34o&a|fQ;\nz&R=xVK8Z-jS8R4m_gj2C$xPq$xv1H553!y9ip8A~vxoyq&fFbw!L$zU@hDh#f$r>>\nzAYPcTf%K%xv5<+JivV@Da!&`-v8eIo5ApKIdiK7E`krA1X!I73&)t#yo}GERUSnBN\nzlK-+<I<**0!u}jvbPz5b{hhe8_PVcR!i8ObFp-W0bqNWKA+W14zV9Mj$a~(KZ-0uf\nz|BVvaf!K}k92MNalP4hQ^Yxqk-*SzY6q{6Cw*+Fr_+LZp=BFzuo3pVX*T?Ic-|hbZ\nz1PurPv|=$EKX~Bz_Bz;JW71^>8;AnsY?5ub{hajO)@PHTt`On@Z;}jyeue<@oS$3l\nzEu9m5O={<O&85HqUN|R$bZiPXX4YsWsPOn@@M)Lf;hyI_(eM$*#Sf3}T=1jzGvsiy\nziKuCmV^)`UC@8ma=jBiHqr!1L4>w`5P=-vvIiJTGj0-lRpc+EU9(`B#rn1m1WqmXr\nz5Z+N|DR*y*k<4fY0I_eP7qVo9Fx58WMwaK^N~I=>1VN29R|Umg^2q$WGlE#9AFC2_\nzOYG5M$}OL?FQ)ej0!KGwY-hAFz#wAPr>3&{`s7!HUZ22Y7KY{9ly1wQ8^hqBojPv8\nzh)Dz(5dr`S1p-EA%T59t3;+rV5EHib9?jlE4Z-XHpqd%~B34SUmtRi+{;vv2ei?p%\neLjau^#pu5ba=n|GS94<zkje^zT1YFf0ssTLm?$>@\n\ndiff --git a/t/t7004/random_seed b/t/t7004/random_seed\ndeleted file mode 100644\nindex 8fed1339ed0a744e5663f4a5e6b6ac9bae3d8524..0000000000000000000000000000000000000000\nGIT binary patch\nliteral 0\nHcmV?d00001\n\nliteral 600\nzcmV-e0;m1=h9nBV>1C6QsKJEiEJaD@Q3F8s5u<$E+<2(By)JAZSxviTsXg(wKC+O%\nzzvV{Z>W3*k?r7~pgmmkbw8-x{Am!eeN)z?cwIHcT2jqgiA(SXo<iO=E?cY80`p#w8\nz)O-&?SnwsJ=1VJ-?26&*g88Nr8E=g2onRW^(c+2nJlX)?dmK)tPO0EY-!B!vMCv1)\nz-AOW(3WuF+7IdSxMnzrDgnMqVU=|+YFxlY|VeR+Fg<%C@0Xupi0<S7QYJyFTR$}FQ\nzzoSAbU>CoCKWKX;!3@L_U=aFUm!M<>ILG}$`bfnadAkLQbI-upV7Qwf^OE&N45Pz<\nzk~^KlzNC6)d@QGv=K5-At&A8FS&MQSR`LB}@R1?A3K1p(vM>7CK}EfFhmBJd&cH^-\nz(3Ih^`VuoVBB|w~p!Q^#DY%V2A2FhXu<Bp*L)lSCUdqRyI5wxMG&E1sL$)E$Zo&pJ\nzgy#;fENqHImgN>LL2!7DhfZ}&;BSAyz=T0#S?2+NET5St@16L?YI?5Io%<uD|2}hl\nzx0xsuefz1+bM^-ZIgtKs=)&VAI8(MfytvM>t>%~nsXUb~*EkptHiN?W{=DRu_s;2u\nziHh{2&>;CQO7;>{$DN33_Ef}g+;b<2hIF^p(Y>^riLBb*Y2Xw>F8)jp49&oLKJOic\nz+V{Lt!_`eKGhyk5Edie{-^#n!TFlsfux*QBRZEh^4SVePPmb{BvF|>sKd2cYg@vKp\nmVI8jcB1(k(tlt^Kr<{EMs>|b*d70nyVMQcc%xEnE(#Uq3d^-35\n\ndiff --git a/t/t7004/secring.gpg b/t/t7004/secring.gpg\ndeleted file mode 100644\nindex d831cd9eb3eee613d3c0e1a71093ae01ea7347e3..0000000000000000000000000000000000000000\nGIT binary patch\nliteral 0\nHcmV?d00001\n\nliteral 1237\nzcmV;`1S<QL0lNf7XUi!O1OT%L_{gO;4KKDfwF;#WWYZ2?*>Ph_j9y>uobJf#jXEbw\nzQUOEXs+@;X*)1kyN*7VwW}f-GD3>Vlu2F6j{T^Kt%(kMo(W3C}W^X)NL|0VdGIzc`\nz5@TY!;Du~AFGW=#0vYgS@U2j8Q{Qe9F~lQEBVt+(yjM1jQ{YLe0lxsC<HX~BiFSU3\nzs3oq~QtxsAzbwuV1N{{&PcudMk6}p><?O}^6vikhMm+>LD}W25r?wdKr$(|R8_;Mn\nz&Yh%2Hbu9IX>#(R8S&@jVI!K^K7AI$=A+*-qJ>-J^fdR_m5GZao*%P{nee=O$Eb$s\nzes#O%4Z_4OAZW}8Ey8%%w->rA>z!nf)&NPikkr&pkRlB>=W+x68fwevWl9Ys2R3m2\nzNPf<ig>x1y^AXyemu>Q}DAp%==Q`5sCrBzLaH^rOm1NLtt-ig*IfY?HMdv66@%u%#\nzy0K62;Z+4O3Ol6i*<7GlXeW6FC?0yV!c_#yGozNjrOE#8+Rw@Z8cGAG=eE%uMUW=V\nzX(76db;AG@lD)hA005tK#LT31ryNoF9o-(`X`Xl5w88@mle8j3AWtAoX>@dDav(fo\nzZ*6U9baZ8MKxKGgZE$R5E@N+PK8Rif6A=Oc9t8qMXUi!98v_Ol2?z%R0s|ES0|OQU\nz0RkQY0vCV)3JDMsw)Gy(-a`#^9RQ%c+5fOQwB&AM#DD9)s1L@!pm9S0pF>@|C2*qL\nz%1j5#NZ6^U*PB>^d9VTi0G$Fo1V(4eP7nwH&bfI%Q|ss;(eh{c3}LfzMGW}yE1(Vq\nzEz*hQmYH+9vesFqTifovzOi;G@N);A+0I{d(LRYt$5(82OZQuRJjqPo^tq_na}Tkd\nz|BA((5wnN`O3vIJaly0>?(ryCcY*Hgm>^!5uz~cX%CV4%oQnW;wsKDg)3K=W<qz@l\nz$a?m^i29yk259sakI&ta{GOe8x?W>hQIh|%Svs{CO~U>hTXYaE9sQlSv-Y~LWWt4A\nze=w1b1$7Atj3KbAF~092T*!Ogn{R)Lum6n_*@4)N@EjG~z>_B+>GSoQ{oitpmlT^+\nzUAF{c!T4W8?B=H{DVwvgAlJw1n&0jJ00a#P0JLH;8$Wp9`Sv>4USrZ_1sjM0<!q8|\nzxc!{;-PUK5pso<&0dJBFgMNkp@|>Sr>@A%Wd`)WSc+I820A4sJf^=*OHfGjnC8+TD\nzW$<a2;o+X=JkjtG#>Eeh?p*Mr_A}&gvx%r_lw($xcPJ>gap&bv^P|FXJr6fwvQUOh\nzz&W4C8jK4zqM#Z=%N~7K_NKDXEM<K(9uVG9XDN4YijmA{1^}^dq8GAcg)r4N<3^U}\nz-b$q=iUdK8Hdh73UGm8MyfcDWr5~#ja!c&dVahF^v@fRj3IazrWNc@&F~A^V)u*Pi\nz`ugNogkGP(V-|+x+mvq0pc}*Bpq)By!2khN4M6SOU21NKpXW5P=UU_LoDzvrTdR;4\nzU)W&&o9V6>MQOk{A%?1(Ozs{Qqlifa7!d*h2?YX1XUk3k8w>yn2@n&u^&ZXMLk+>~\nz0HDxvfGrJTMY@$ys#k#t?D63Zq=W#R3eUeT(L61`VGHe?zP;`rnta#-umS)8nA%Rq\n\ndiff --git a/t/t7004/trustdb.gpg b/t/t7004/trustdb.gpg\ndeleted file mode 100644\nindex abace962b8bf84be688a6f27e4ebd0ee7052f210..0000000000000000000000000000000000000000\nGIT binary patch\nliteral 0\nHcmV?d00001\n\nliteral 1280\nzcmZQfFGy!*W@Ke#U|?`dKkWykumMIcY@%4iM%7^n6rj+M4;MLzzlOX&pwTnxkD-}P\nzc^HbXN0fL!SIq1?>env3?W^3`d(OOU5YNaX{KU(k^<0;M@87ONv)_6ZxD={-=<kYO\nM2Ud3=2BC}r0AuhNr2qf`\n\n-- \n1.7.7.498.g3f2e50\n"},{"id":"178057","messageId":"1319071023-31919-6-git-send-email-gitster@pobox.com","threadId":"28598","inReplyTo":"1319071023-31919-1-git-send-email-gitster@pobox.com","subject":"[PATCH v4 5/5] test \"commit -S\" and \"log --show-signature\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-10-20T00:37:03Z","receivedAt":"2011-10-20T00:37:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Signed-off-by: Junio C Hamano <gitster@pobox.com>\n---\n t/t7510-signed-commit.sh |   60 ++++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 60 insertions(+), 0 deletions(-)\n create mode 100755 t/t7510-signed-commit.sh\n\ndiff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh\nnew file mode 100755\nindex 0000000..5c7475d\n--- /dev/null\n+++ b/t/t7510-signed-commit.sh\n@@ -0,0 +1,60 @@\n+#!/bin/sh\n+\n+test_description='signed commit tests'\n+. ./test-lib.sh\n+. \"$TEST_DIRECTORY/lib-gpg.sh\"\n+\n+test_expect_success GPG 'create signed commits' '\n+\techo 1 >file && git add file &&\n+\ttest_tick && git commit -S -m initial &&\n+\tgit tag initial &&\n+\tgit branch side &&\n+\n+\techo 2 >file && test_tick && git commit -a -S -m second &&\n+\tgit tag second &&\n+\n+\tgit checkout side &&\n+\techo 3 >elif && git add elif &&\n+\ttest_tick && git commit -m \"third on side\" &&\n+\n+\tgit checkout master &&\n+\ttest_tick && git merge -S side &&\n+\tgit tag merge &&\n+\n+\techo 4 >file && test_tick && git commit -a -m \"fourth unsigned\" &&\n+\tgit tag fourth-unsigned &&\n+\n+\ttest_tick && git commit --amend -S -m \"fourth signed\"\n+'\n+\n+test_expect_success GPG 'show signatures' '\n+\t(\n+\t\tfor commit in initial second merge master\n+\t\tdo\n+\t\t\tgit show --pretty=short --show-signature $commit >actual &&\n+\t\t\tgrep \"Good signature from\" actual || exit 1\n+\t\t\t! grep \"BAD signature from\" actual || exit 1\n+\t\t\techo $commit OK\n+\t\tdone\n+\t) &&\n+\t(\n+\t\tfor commit in merge^2 fourth-unsigned\n+\t\tdo\n+\t\t\tgit show --pretty=short --show-signature $commit >actual &&\n+\t\t\tgrep \"Good signature from\" actual && exit 1\n+\t\t\t! grep \"BAD signature from\" actual || exit 1\n+\t\t\techo $commit OK\n+\t\tdone\n+\t)\n+'\n+\n+test_expect_success GPG 'detect fudged signature' '\n+\tgit cat-file commit master >raw &&\n+\tsed -e \"s/fourth signed/4th forged/\" raw >forged &&\n+\tgit hash-object -w -t commit forged >forged.commit &&\n+\tgit show --pretty=short --show-signature $(cat forged.commit) >actual &&\n+\tgrep \"BAD signature from\" actual &&\n+\t! grep \"Good signature from\" actual\n+'\n+\n+test_done\n-- \n1.7.7.498.g3f2e50\n"},{"id":"178157","messageId":"7v7h3x7h6j.fsf_-_@alter.siamese.dyndns.org","threadId":"28598","inReplyTo":"1319071023-31919-1-git-send-email-gitster@pobox.com","subject":"[PATCH 7/5] pretty: %G[?GS] placeholders","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-10-22T05:01:56Z","receivedAt":"2011-10-22T05:01:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Add new placeholders related to the GPG signature on signed commits.\n\n - %GG to show the raw verification message from GPG;\n - %G? to show either \"G\" for Good, \"B\" for Bad;\n - %GS to show the name of the signer.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n * The 6th is the one that works with a bogus commit with NUL in it I sent\n   out previously.\n\n   This concludes the series; I'll leave the design and implementation of\n   other useful placeholders to the list for now.\n\n pretty.c |   86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 86 insertions(+), 0 deletions(-)\n\ndiff --git a/pretty.c b/pretty.c\nindex f45eb54..392d656 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -9,6 +9,7 @@\n #include \"notes.h\"\n #include \"color.h\"\n #include \"reflog-walk.h\"\n+#include \"gpg-interface.h\"\n \n static char *user_format;\n static struct cmt_fmt_map {\n@@ -640,6 +641,12 @@ struct format_commit_context {\n \tconst struct pretty_print_context *pretty_ctx;\n \tunsigned commit_header_parsed:1;\n \tunsigned commit_message_parsed:1;\n+\tunsigned commit_signature_parsed:1;\n+\tstruct {\n+\t\tchar *gpg_output;\n+\t\tchar good_bad;\n+\t\tchar *signer;\n+\t} signature;\n \tchar *message;\n \tsize_t width, indent1, indent2;\n \n@@ -822,6 +829,59 @@ static void rewrap_message_tail(struct strbuf *sb,\n \tc->indent2 = new_indent2;\n }\n \n+static struct {\n+\tchar result;\n+\tconst char *check;\n+} signature_check[] = {\n+\t{ 'G', \": Good signature from \" },\n+\t{ 'B', \": BAD signature from \" },\n+};\n+\n+static void parse_signature_lines(struct format_commit_context *ctx)\n+{\n+\tconst char *buf = ctx->signature.gpg_output;\n+\tint i;\n+\n+\tfor (i = 0; i < ARRAY_SIZE(signature_check); i++) {\n+\t\tconst char *found = strstr(buf, signature_check[i].check);\n+\t\tconst char *next;\n+\t\tif (!found)\n+\t\t\tcontinue;\n+\t\tctx->signature.good_bad = signature_check[i].result;\n+\t\tfound += strlen(signature_check[i].check);\n+\t\tnext = strchrnul(found, '\\n');\n+\t\tctx->signature.signer = xmemdupz(found, next - found);\n+\t\tbreak;\n+\t}\n+}\n+\n+static void parse_commit_signature(struct format_commit_context *ctx)\n+{\n+\tstruct strbuf payload = STRBUF_INIT;\n+\tstruct strbuf signature = STRBUF_INIT;\n+\tstruct strbuf gpg_output = STRBUF_INIT;\n+\tint status;\n+\n+\tctx->commit_signature_parsed = 1;\n+\n+\tif (parse_signed_commit(ctx->commit->object.sha1,\n+\t\t\t\t&payload, &signature) <= 0)\n+\t\tgoto out;\n+\tstatus = verify_signed_buffer(payload.buf, payload.len,\n+\t\t\t\t      signature.buf, signature.len,\n+\t\t\t\t      &gpg_output);\n+\tif (status && !gpg_output.len)\n+\t\tgoto out;\n+\tctx->signature.gpg_output = strbuf_detach(&gpg_output, NULL);\n+\tparse_signature_lines(ctx);\n+\n+ out:\n+\tstrbuf_release(&gpg_output);\n+\tstrbuf_release(&payload);\n+\tstrbuf_release(&signature);\n+}\n+\n+\n static size_t format_commit_one(struct strbuf *sb, const char *placeholder,\n \t\t\t\tvoid *context)\n {\n@@ -974,6 +1034,30 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,\n \t\treturn 0;\n \t}\n \n+\tif (placeholder[0] == 'G') {\n+\t\tif (!c->commit_signature_parsed)\n+\t\t\tparse_commit_signature(c);\n+\t\tswitch (placeholder[1]) {\n+\t\tcase 'G':\n+\t\t\tif (c->signature.gpg_output)\n+\t\t\t\tstrbuf_addstr(sb, c->signature.gpg_output);\n+\t\t\tbreak;\n+\t\tcase '?':\n+\t\t\tswitch (c->signature.good_bad) {\n+\t\t\tcase 'G':\n+\t\t\tcase 'B':\n+\t\t\t\tstrbuf_addch(sb, c->signature.good_bad);\n+\t\t\t}\n+\t\t\tbreak;\n+\t\tcase 'S':\n+\t\t\tif (c->signature.signer)\n+\t\t\t\tstrbuf_addstr(sb, c->signature.signer);\n+\t\t\tbreak;\n+\t\t}\n+\t\treturn 2;\n+\t}\n+\n+\n \t/* For the rest we have to parse the commit header. */\n \tif (!c->commit_header_parsed)\n \t\tparse_commit_header(c);\n@@ -1114,6 +1198,8 @@ void format_commit_message(const struct commit *commit,\n \n \tif (context.message != commit->buffer)\n \t\tfree(context.message);\n+\tfree(context.signature.gpg_output);\n+\tfree(context.signature.signer);\n }\n \n static void pp_header(const struct pretty_print_context *pp,\n-- \n1.7.7.555.g02edb3\n"},{"id":"178159","messageId":"CA+EOSB=EowzV5B9jjq9D9rshPt8LmO9K_GbwNWo_x3Uv9+kwxg@mail.gmail.com","threadId":"28598","inReplyTo":"7v7h3x7h6j.fsf_-_@alter.siamese.dyndns.org","subject":"Re: [PATCH 7/5] pretty: %G[?GS] placeholders","fromName":"Elia Pinto","fromEmail":"gitter.spiros@gmail.com","sentAt":"2011-10-22T10:47:07Z","receivedAt":"2011-10-22T10:47:07Z","isPatch":true,"sender":{"key":"gitter.spiros@gmail.com","avatar":"https://avatars.githubusercontent.com/u/158490?v=4"},"body":"Can you suggest what do you think can be useful placeholders ? Thanks.\n\n2011/10/22, Junio C Hamano <gitster@pobox.com>:\n> Add new placeholders related to the GPG signature on signed commits.\n>\n>  - %GG to show the raw verification message from GPG;\n>  - %G? to show either \"G\" for Good, \"B\" for Bad;\n>  - %GS to show the name of the signer.\n>\n> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n> ---\n>  * The 6th is the one that works with a bogus commit with NUL in it I sent\n>    out previously.\n>\n>    This concludes the series; I'll leave the design and implementation of\n>    other useful placeholders to the list for now.\n>\n>  pretty.c |   86\n> ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n>  1 files changed, 86 insertions(+), 0 deletions(-)\n>\n> diff --git a/pretty.c b/pretty.c\n> index f45eb54..392d656 100644\n> --- a/pretty.c\n> +++ b/pretty.c\n> @@ -9,6 +9,7 @@\n>  #include \"notes.h\"\n>  #include \"color.h\"\n>  #include \"reflog-walk.h\"\n> +#include \"gpg-interface.h\"\n>\n>  static char *user_format;\n>  static struct cmt_fmt_map {\n> @@ -640,6 +641,12 @@ struct format_commit_context {\n>  \tconst struct pretty_print_context *pretty_ctx;\n>  \tunsigned commit_header_parsed:1;\n>  \tunsigned commit_message_parsed:1;\n> +\tunsigned commit_signature_parsed:1;\n> +\tstruct {\n> +\t\tchar *gpg_output;\n> +\t\tchar good_bad;\n> +\t\tchar *signer;\n> +\t} signature;\n>  \tchar *message;\n>  \tsize_t width, indent1, indent2;\n>\n> @@ -822,6 +829,59 @@ static void rewrap_message_tail(struct strbuf *sb,\n>  \tc->indent2 = new_indent2;\n>  }\n>\n> +static struct {\n> +\tchar result;\n> +\tconst char *check;\n> +} signature_check[] = {\n> +\t{ 'G', \": Good signature from \" },\n> +\t{ 'B', \": BAD signature from \" },\n> +};\n> +\n> +static void parse_signature_lines(struct format_commit_context *ctx)\n> +{\n> +\tconst char *buf = ctx->signature.gpg_output;\n> +\tint i;\n> +\n> +\tfor (i = 0; i < ARRAY_SIZE(signature_check); i++) {\n> +\t\tconst char *found = strstr(buf, signature_check[i].check);\n> +\t\tconst char *next;\n> +\t\tif (!found)\n> +\t\t\tcontinue;\n> +\t\tctx->signature.good_bad = signature_check[i].result;\n> +\t\tfound += strlen(signature_check[i].check);\n> +\t\tnext = strchrnul(found, '\\n');\n> +\t\tctx->signature.signer = xmemdupz(found, next - found);\n> +\t\tbreak;\n> +\t}\n> +}\n> +\n> +static void parse_commit_signature(struct format_commit_context *ctx)\n> +{\n> +\tstruct strbuf payload = STRBUF_INIT;\n> +\tstruct strbuf signature = STRBUF_INIT;\n> +\tstruct strbuf gpg_output = STRBUF_INIT;\n> +\tint status;\n> +\n> +\tctx->commit_signature_parsed = 1;\n> +\n> +\tif (parse_signed_commit(ctx->commit->object.sha1,\n> +\t\t\t\t&payload, &signature) <= 0)\n> +\t\tgoto out;\n> +\tstatus = verify_signed_buffer(payload.buf, payload.len,\n> +\t\t\t\t      signature.buf, signature.len,\n> +\t\t\t\t      &gpg_output);\n> +\tif (status && !gpg_output.len)\n> +\t\tgoto out;\n> +\tctx->signature.gpg_output = strbuf_detach(&gpg_output, NULL);\n> +\tparse_signature_lines(ctx);\n> +\n> + out:\n> +\tstrbuf_release(&gpg_output);\n> +\tstrbuf_release(&payload);\n> +\tstrbuf_release(&signature);\n> +}\n> +\n> +\n>  static size_t format_commit_one(struct strbuf *sb, const char *placeholder,\n>  \t\t\t\tvoid *context)\n>  {\n> @@ -974,6 +1034,30 @@ static size_t format_commit_one(struct strbuf *sb,\n> const char *placeholder,\n>  \t\treturn 0;\n>  \t}\n>\n> +\tif (placeholder[0] == 'G') {\n> +\t\tif (!c->commit_signature_parsed)\n> +\t\t\tparse_commit_signature(c);\n> +\t\tswitch (placeholder[1]) {\n> +\t\tcase 'G':\n> +\t\t\tif (c->signature.gpg_output)\n> +\t\t\t\tstrbuf_addstr(sb, c->signature.gpg_output);\n> +\t\t\tbreak;\n> +\t\tcase '?':\n> +\t\t\tswitch (c->signature.good_bad) {\n> +\t\t\tcase 'G':\n> +\t\t\tcase 'B':\n> +\t\t\t\tstrbuf_addch(sb, c->signature.good_bad);\n> +\t\t\t}\n> +\t\t\tbreak;\n> +\t\tcase 'S':\n> +\t\t\tif (c->signature.signer)\n> +\t\t\t\tstrbuf_addstr(sb, c->signature.signer);\n> +\t\t\tbreak;\n> +\t\t}\n> +\t\treturn 2;\n> +\t}\n> +\n> +\n>  \t/* For the rest we have to parse the commit header. */\n>  \tif (!c->commit_header_parsed)\n>  \t\tparse_commit_header(c);\n> @@ -1114,6 +1198,8 @@ void format_commit_message(const struct commit\n> *commit,\n>\n>  \tif (context.message != commit->buffer)\n>  \t\tfree(context.message);\n> +\tfree(context.signature.gpg_output);\n> +\tfree(context.signature.signer);\n>  }\n>\n>  static void pp_header(const struct pretty_print_context *pp,\n> --\n> 1.7.7.555.g02edb3\n>\n> --\n> To unsubscribe from this list: send the line \"unsubscribe git\" in\n> the body of a message to majordomo@vger.kernel.org\n> More majordomo info at  http://vger.kernel.org/majordomo-info.html\n>\n\n-- \nInviato dal mio dispositivo mobile\n"},{"id":"178169","messageId":"7vsjmk6hdi.fsf@alter.siamese.dyndns.org","threadId":"28598","inReplyTo":"CA+EOSB=EowzV5B9jjq9D9rshPt8LmO9K_GbwNWo_x3Uv9+kwxg@mail.gmail.com","subject":"Re: [PATCH 7/5] pretty: %G[?GS] placeholders","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-10-22T17:55:21Z","receivedAt":"2011-10-22T17:55:21Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elia Pinto <gitter.spiros@gmail.com> writes:\n\n> Can you suggest what do you think can be useful placeholders ? Thanks.\n\nThat is a weird question.\n\n> 2011/10/22, Junio C Hamano <gitster@pobox.com>:\n>> Add new placeholders related to the GPG signature on signed commits.\n>>\n>>  - %GG to show the raw verification message from GPG;\n>>  - %G? to show either \"G\" for Good, \"B\" for Bad;\n>>  - %GS to show the name of the signer.\n>>\n>> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n>> ---\n>>  * The 6th is the one that works with a bogus commit with NUL in it I sent\n>>    out previously.\n>>\n>>    This concludes the series; I'll leave the design and implementation of\n>>    other useful placeholders to the list for now.\n\nI can think of random other placeholders off the top of my head purely by\nspeculation without having real need [*1*], but they won't be much useful.\n\nPeople on the list who *want* to use this feature in their projects may\nhave specific needs and they would be closer to what is needed in the real\nworld use cases than what comes out of thin air by imagination.\n\nThat is the reason why I left the enhancement to the list.\n\nIf you have to ask that question because you do not have any specific need\nyourself, and especially if you have to ask it to *me*, then you should\nwait for others to come up with their real needs, just like what I am\ndoing right now ;-).\n\n\n[Footnote]\n\n*1*\n\n - %GC that is replaced with COLOR_GREEN when Good signature is found,\n   COLOR_RED when BAD signature is found, and COLOR_RESET when there is no\n   signature;\n\n - %GD for the date the signature was made on (with date format variants);\n\n - %Gk for the type of the key and the Key-ID.\n \n"}]}