{"thread":{"id":"28338","subject":"[PATCH v2 1/7] send-pack: typofix error message","startedAt":"2011-09-08T20:01:35Z","lastAt":"2011-09-11T15:51:46Z","messageCount":22,"participants":["Junio C Hamano","Sverre Rabbelier","Robin H. Johnson","Ted Ts'o"],"isPatch":true,"patchVersion":2,"patchTotal":7},"messages":[{"id":"175109","messageId":"1315512102-19022-1-git-send-email-gitster@pobox.com","threadId":"28338","inReplyTo":null,"subject":"[PATCH v2 0/7] \"push -s\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-09-08T20:01:35Z","receivedAt":"2011-09-08T20:01:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Slightly re-rolled from last night.\n\n - Marked push-certificate format as version 0 while we are\n   still experimenting;\n - The push certificate records new object name and the refname;\n - Add support for an external hook \"pre-receive-signature\".\n\nOne issue internally debated was if we want to list the refs that matched\nthe pushing criteria but were found to be already up to date, and this can\nbe argued both ways.\n\n - You can say that you are making assertion that you want to have a\n   certain object at that ref, in which case you would want to include\n   them.\n\n - On the other hand, for the purpose of leaving audit-trail, if the ref\n   you tried to push already had the object you wanted to see at the tip\n   of a ref, you weren't the person who made the ref point at the object,\n   and it would be sensible not to include them.\n\nTaking the latter stance is slightly easier on the end users, because\n\"Everything up-to-date\" case becomes a no-op as the natural consequence,\nand we do not have to ask them to unlock their GPG key in such a case.  It\nhowever makes it impossible to say \"Earlier I pushed that object to the\ntip of my branch but forgot to sign the push, and I want to make a signed\npush, even though I didn't add anything to my history.\"\n\nPeople who configured to push out more than one branches with \"git push\"\noften work on one branch, run \"git push\" which ends up pushing that branch\nbut not other branches, then work on another branch and run \"git push\" to\npush out that other branch, while the branch he earlier pushed out stays\nthe same since his last push. For such people, the first \"push\" is not\nnecessarily even an assertion that he wants to have both branches pointing\nat certain commits, and from that point of view, not including the latter\nbranch he hasn't worked on (and stayed up-to-date) in the push certifiate\nis a sensible thing to do.\n\nAs there is no single right answer, this round of re-roll keeps the latter\nsemantics to record only what you pushed out as the original series.\n\nJunio C Hamano (7):\n  send-pack: typofix error message\n  Split GPG interface into its own helper library\n  push -s: skeleton\n  push -s: send signed push certificate\n  push -s: receiving end\n  refactor run_receive_hook()\n  push -s: support pre-receive-signature hook\n\n Makefile               |    2 +\n builtin/push.c         |    1 +\n builtin/receive-pack.c |  206 +++++++++++++++++++++++++++++++++++++++++++-----\n builtin/send-pack.c    |   61 +++++++++++++-\n builtin/tag.c          |   60 ++------------\n builtin/verify-tag.c   |   35 +--------\n gpg-interface.c        |   94 ++++++++++++++++++++++\n gpg-interface.h        |   11 +++\n send-pack.h            |    1 +\n transport.c            |    4 +\n transport.h            |    4 +\n 11 files changed, 369 insertions(+), 110 deletions(-)\n create mode 100644 gpg-interface.c\n create mode 100644 gpg-interface.h\n\n-- \n1.7.7.rc0.188.g3793ac\n"},{"id":"175102","messageId":"1315512102-19022-2-git-send-email-gitster@pobox.com","threadId":"28338","inReplyTo":"1315512102-19022-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 1/7] send-pack: typofix error message","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-09-08T20:01:36Z","receivedAt":"2011-09-08T20:01:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The message identifies the process as receive-pack when it cannot fork the\nsideband demultiplexer. We are actually a send-pack.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/send-pack.c |    2 +-\n 1 files changed, 1 insertions(+), 1 deletions(-)\n\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex c1f6ddd..87833f4 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -334,7 +334,7 @@ int send_pack(struct send_pack_args *args,\n \t\tdemux.data = fd;\n \t\tdemux.out = -1;\n \t\tif (start_async(&demux))\n-\t\t\tdie(\"receive-pack: unable to fork off sideband demultiplexer\");\n+\t\t\tdie(\"send-pack: unable to fork off sideband demultiplexer\");\n \t\tin = demux.out;\n \t}\n \n-- \n1.7.7.rc0.188.g3793ac\n"},{"id":"175107","messageId":"1315512102-19022-3-git-send-email-gitster@pobox.com","threadId":"28338","inReplyTo":"1315512102-19022-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 2/7] Split GPG interface into its own helper library","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-09-08T20:01:37Z","receivedAt":"2011-09-08T20:01:37Z","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\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Makefile             |    2 +\n builtin/tag.c        |   60 ++++----------------------------\n builtin/verify-tag.c |   35 ++-----------------\n gpg-interface.c      |   94 ++++++++++++++++++++++++++++++++++++++++++++++++++\n gpg-interface.h      |   11 ++++++\n 5 files changed, 117 insertions(+), 85 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..e9d36fa 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@@ -208,60 +209,13 @@ 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+\tconst char *key;\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+\tif (*signingkey)\n+\t\tkey = signingkey;\n+\telse\n+\t\tkey = git_committer_info(IDENT_ERROR_ON_NO_NAME|IDENT_NO_DATE);\n+\treturn sign_buffer(buffer, key);\n }\n \n static const char tag_template[] =\ndiff --git a/builtin/verify-tag.c b/builtin/verify-tag.c\nindex 3134766..8b4f742 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,12 @@ 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-\n-\tunlink_or_warn(path);\n-\n-\treturn ret;\n+\treturn verify_signed_buffer(buf, size, len);\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..b83cca1\n--- /dev/null\n+++ b/gpg-interface.c\n@@ -0,0 +1,94 @@\n+/*\n+ * Copyright (c) 2011, Google Inc.\n+ */\n+#include \"cache.h\"\n+#include \"run-command.h\"\n+#include \"strbuf.h\"\n+#include \"gpg-interface.h\"\n+#include \"sigchain.h\"\n+\n+int sign_buffer(struct strbuf *buffer, const char *signing_key)\n+{\n+\tstruct child_process gpg;\n+\tconst char *args[4];\n+\tssize_t len;\n+\tint i, j;\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+\tlen = strbuf_read(buffer, 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 = 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+}\n+\n+int verify_signed_buffer(const char *buf, size_t total, size_t payload)\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, buf, total) < 0)\n+\t\treturn error(\"failed writing temporary file '%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+\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, payload);\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..7689357\n--- /dev/null\n+++ b/gpg-interface.h\n@@ -0,0 +1,11 @@\n+#ifndef GPG_INTERFACE_H\n+#define GPG_INTERFACE_H\n+\n+/*\n+ * Copyright (c) 2011, Google Inc.\n+ */\n+\n+extern int sign_buffer(struct strbuf *buffer, const char *signing_key);\n+extern int verify_signed_buffer(const char *buffer, size_t total, size_t payload);\n+\n+#endif\n-- \n1.7.7.rc0.188.g3793ac\n"},{"id":"175111","messageId":"1315512102-19022-4-git-send-email-gitster@pobox.com","threadId":"28338","inReplyTo":"1315512102-19022-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 3/7] push -s: skeleton","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-09-08T20:01:38Z","receivedAt":"2011-09-08T20:01:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"If a tag is GPG-signed, and if you trust the cryptographic robustness of\nthe SHA-1 hash and GPG, you can sleep well knowing that all the history\nleading to the signed commit cannot be tampered with. However, it would be\nboth cumbersome and cluttering to sign each and every commit. Especially\nif you strive to keep your history clean by tweaking, rewriting and\npolishing your commits before pushing the resulting history out, many\ncommits you will create locally end up not mattering at all, and it is a\nwaste of time to sign them all.\n\nA better alternative could be to sign a \"push certificate\" (for the lack\nof better name) every time you push, asserting that what commits you are\npushing to update which refs.\n\nThe basic workflow based on this idea would go like this:\n\n 1. You push out your work with \"git push -s\";\n\n 2. \"git push\", as usual, learns where the remote refs are and which refs\n    are to be updated with this push. It prepares a text file in core,\n    that looks like the following:\n\n\tPush-Certificate-Version: 0\n\tPusher: Junio C Hamano <gitster@pobox.com> 1315427886 -0700\n\tUpdate: 3793ac56b4c4f9bf0bddc306a0cec21118683728 refs/heads/master\n\tUpdate: 12850bec0c24b529c9a9df6a95ad4bdeea39373e refs/heads/next\n\n    Each \"Update\" line shows the new object name at the tip of the ref\n    this push tries to update.\n\n    The user then is asked to sign this push certificate using GPG. The\n    result is carried to the other side (i.e. receive-pack). In the\n    protocol exchange, this step comes immediately after the sender tells\n    what the result of the push should be, before it sends the pack data.\n\n 3. The receiving end keeps the signed push certificate in core, receives\n    the pack data and unpacks (or stores and indexes) it as usual.\n\n 4. A new phase to record the push certificate is introduced in the\n    codepath after the receiving end runs receive_hook(). It is envisioned\n    that this phase:\n\n    a. parses the updated-to object names, and appends the push\n       certificate (still GPG signed) to a note attached to each of the\n       objects that will sit at the tip of the refs;\n\n    b. verifies that the push certificate is signed with a GPG key that is\n       authorized to push into this repository; and/or\n\n    c. invokes pre-receive-signature hook, feeds the push certificate to it\n       and asks it to veto the ref updates.\n\nAnd here is a skeleton to implement this. The patch has the necessary\nprotocol extensions implemented (although I do not know if we need\nseparate codepath for stateless RPC mode), but does not have subroutines\nto:\n\n - Sign the certificate with GPG key;\n\n - Parse the signed certificate to identify the updated-to objects, and\n   add the certificate as notes to them;\n\n - Verify the certificate and find out what GPG key was used to sign it;\n   or\n\n - Invoke and feed the certificate to pre-receive-signature hook.\n\nall of which should be fairly trivial. The places that needs to implement\nthese are clearly marked with large comments, so I'll leave it up to other\npeople who are interested in the topic to fill in the blanks ;-)\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/push.c         |    1 +\n builtin/receive-pack.c |   54 +++++++++++++++++++++++++++++++++++++++-\n builtin/send-pack.c    |   64 +++++++++++++++++++++++++++++++++++++++++++++---\n send-pack.h            |    1 +\n transport.c            |    4 +++\n transport.h            |    4 +++\n 6 files changed, 123 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/push.c b/builtin/push.c\nindex 35cce53..2238f4e 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -261,6 +261,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)\n \t\tOPT_BIT('u', \"set-upstream\", &flags, \"set upstream for git pull/status\",\n \t\t\tTRANSPORT_PUSH_SET_UPSTREAM),\n \t\tOPT_BOOLEAN(0, \"progress\", &progress, \"force progress reporting\"),\n+\t\tOPT_BIT('s', \"signed\", &flags, \"GPG sign the push\", TRANSPORT_PUSH_SIGNED),\n \t\tOPT_END()\n \t};\n \ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex ae164da..20b6799 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -30,12 +30,14 @@ static int receive_unpack_limit = -1;\n static int transfer_unpack_limit = -1;\n static int unpack_limit = 100;\n static int report_status;\n+static int signed_push;\n static int use_sideband;\n static int prefer_ofs_delta = 1;\n static int auto_update_server_info;\n static int auto_gc = 1;\n static const char *head_name;\n static int sent_capabilities;\n+static char *push_certificate;\n \n static enum deny_action parse_deny_action(const char *var, const char *value)\n {\n@@ -114,7 +116,7 @@ static int show_ref(const char *path, const unsigned char *sha1, int flag, void\n \telse\n \t\tpacket_write(1, \"%s %s%c%s%s\\n\",\n \t\t\t     sha1_to_hex(sha1), path, 0,\n-\t\t\t     \" report-status delete-refs side-band-64k\",\n+\t\t\t     \" report-status delete-refs side-band-64k signed-push\",\n \t\t\t     prefer_ofs_delta ? \" ofs-delta\" : \"\");\n \tsent_capabilities = 1;\n \treturn 0;\n@@ -579,6 +581,31 @@ static void check_aliased_updates(struct command *commands)\n \tstring_list_clear(&ref_list, 0);\n }\n \n+static int record_signed_push(char *cert)\n+{\n+\t/*\n+\t * This is the place for you to parse the signed push\n+\t * certificate, grab the commit object names the push updates\n+\t * refs to, and append the certificate to the notes to these\n+\t * commits.\n+\t *\n+\t * You could also feed the signed push certificate to GPG,\n+\t * verify the signer identity, and all the other fun stuff,\n+\t * including feeding it to \"pre-receive-signature\" hook.\n+\t *\n+\t * Here we just throw it to stderr to demonstrate that the\n+\t * codepath is being exercised.\n+\t */\n+\tchar *cp, *ep;\n+\tfor (cp = cert; *cp; cp = ep) {\n+\t\tep = strchrnul(cp, '\\n');\n+\t\tif (*ep == '\\n')\n+\t\t\tep++;\n+\t\tfprintf(stderr, \"RSP: %.*s\", (int)(ep - cp), cp);\n+\t}\n+\treturn 0;\n+}\n+\n static void execute_commands(struct command *commands, const char *unpacker_error)\n {\n \tstruct command *cmd;\n@@ -596,6 +623,12 @@ static void execute_commands(struct command *commands, const char *unpacker_erro\n \t\treturn;\n \t}\n \n+\tif (push_certificate && record_signed_push(push_certificate)) {\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\t\tcmd->error_string = \"n/a (push signature error)\";\n+\t\treturn;\n+\t}\n+\n \tcheck_aliased_updates(commands);\n \n \thead_name = resolve_ref(\"HEAD\", sha1, 0, NULL);\n@@ -636,6 +669,8 @@ static struct command *read_head_info(void)\n \t\t\t\treport_status = 1;\n \t\t\tif (strstr(refname + reflen + 1, \"side-band-64k\"))\n \t\t\t\tuse_sideband = LARGE_PACKET_MAX;\n+\t\t\tif (strstr(refname + reflen + 1, \"signed-push\"))\n+\t\t\t\tsigned_push = 1;\n \t\t}\n \t\tcmd = xcalloc(1, sizeof(struct command) + len - 80);\n \t\thashcpy(cmd->old_sha1, old_sha1);\n@@ -731,6 +766,21 @@ static const char *unpack(void)\n \t}\n }\n \n+static char *receive_push_certificate(void)\n+{\n+\tstruct strbuf cert = STRBUF_INIT;\n+\tfor (;;) {\n+\t\tchar line[1000];\n+\t\tint len;\n+\n+\t\tlen = packet_read_line(0, line, sizeof(line));\n+\t\tif (!len)\n+\t\t\tbreak;\n+\t\tstrbuf_add(&cert, line, len);\n+\t}\n+\treturn strbuf_detach(&cert, NULL);\n+}\n+\n static void report(struct command *commands, const char *unpack_status)\n {\n \tstruct command *cmd;\n@@ -846,6 +896,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)\n \tif ((commands = read_head_info()) != NULL) {\n \t\tconst char *unpack_status = NULL;\n \n+\t\tif (signed_push)\n+\t\t\tpush_certificate = receive_push_certificate();\n \t\tif (!delete_only(commands))\n \t\t\tunpack_status = unpack();\n \t\texecute_commands(commands, unpack_status);\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex 87833f4..7f4778c 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -237,6 +237,27 @@ static int sideband_demux(int in, int out, void *data)\n \treturn ret;\n }\n \n+static void sign_push_certificate(struct strbuf *cert)\n+{\n+\t/*\n+\t * Here, take the contents of cert->buf, and have the user GPG\n+\t * sign it, and read it back in the strbuf.\n+\t *\n+\t * You may want to append some extra info to cert before giving\n+\t * it to GPG, possibly via a hook.\n+\t *\n+\t * Here we upcase them just to demonstrate that the codepath\n+\t * is being exercised.\n+\t */\n+\tchar *cp;\n+\tfor (cp = cert->buf; *cp; cp++) {\n+\t\tint ch = *cp;\n+\t\tif ('a' <= ch && ch <= 'z')\n+\t\t\t*cp = toupper(ch);\n+\t}\n+\treturn;\n+}\n+\n int send_pack(struct send_pack_args *args,\n \t      int fd[], struct child_process *conn,\n \t      struct ref *remote_refs,\n@@ -250,9 +271,11 @@ int send_pack(struct send_pack_args *args,\n \tint allow_deleting_refs = 0;\n \tint status_report = 0;\n \tint use_sideband = 0;\n+\tint signed_push = 0;\n \tunsigned cmds_sent = 0;\n \tint ret;\n \tstruct async demux;\n+\tstruct strbuf push_cert = STRBUF_INIT;\n \n \t/* Does the other end support the reporting? */\n \tif (server_supports(\"report-status\"))\n@@ -270,6 +293,18 @@ int send_pack(struct send_pack_args *args,\n \t\treturn 0;\n \t}\n \n+\tif (args->signed_push) {\n+\t\tif (server_supports(\"signed-push\"))\n+\t\t\tsigned_push = !args->dry_run;\n+\t\telse\n+\t\t\twarning(\"The receiving side does not support signed-push\");\n+\t}\n+\n+\tif (signed_push) {\n+\t\tstrbuf_addstr(&push_cert, \"Push-Certificate-Version: 0\\n\");\n+\t\tstrbuf_addf(&push_cert, \"Pusher: %s\\n\", git_committer_info(0));\n+\t}\n+\n \t/*\n \t * Finally, tell the other end!\n \t */\n@@ -301,15 +336,19 @@ int send_pack(struct send_pack_args *args,\n \t\t\tchar *old_hex = sha1_to_hex(ref->old_sha1);\n \t\t\tchar *new_hex = sha1_to_hex(ref->new_sha1);\n \n-\t\t\tif (!cmds_sent && (status_report || use_sideband)) {\n-\t\t\t\tpacket_buf_write(&req_buf, \"%s %s %s%c%s%s\",\n+\t\t\tif (!cmds_sent &&\n+\t\t\t    (status_report || use_sideband || signed_push))\n+\t\t\t\tpacket_buf_write(&req_buf, \"%s %s %s%c%s%s%s\",\n \t\t\t\t\told_hex, new_hex, ref->name, 0,\n \t\t\t\t\tstatus_report ? \" report-status\" : \"\",\n-\t\t\t\t\tuse_sideband ? \" side-band-64k\" : \"\");\n-\t\t\t}\n+\t\t\t\t\tuse_sideband ? \" side-band-64k\" : \"\",\n+\t\t\t\t\tsigned_push ? \" signed-push\" : \"\");\n \t\t\telse\n \t\t\t\tpacket_buf_write(&req_buf, \"%s %s %s\",\n \t\t\t\t\told_hex, new_hex, ref->name);\n+\t\t\tif (signed_push)\n+\t\t\t\tstrbuf_addf(&push_cert, \"Update: %s %s\\n\",\n+\t\t\t\t\t    new_hex, ref->name);\n \t\t\tref->status = status_report ?\n \t\t\t\tREF_STATUS_EXPECTING_REPORT :\n \t\t\t\tREF_STATUS_OK;\n@@ -326,6 +365,23 @@ int send_pack(struct send_pack_args *args,\n \t\tsafe_write(out, req_buf.buf, req_buf.len);\n \t\tpacket_flush(out);\n \t}\n+\n+\tif (signed_push && cmds_sent) {\n+\t\tchar *cp, *ep;\n+\n+\t\tsign_push_certificate(&push_cert);\n+\t\tstrbuf_reset(&req_buf);\n+\t\tfor (cp = push_cert.buf; *cp; cp = ep) {\n+\t\t\tep = strchrnul(cp, '\\n');\n+\t\t\tif (*ep == '\\n')\n+\t\t\t\tep++;\n+\t\t\tpacket_buf_write(&req_buf, \"%.*s\",\n+\t\t\t\t\t (int)(ep - cp), cp);\n+\t\t}\n+\t\t/* Do we need anything funky for stateless rpc? */\n+\t\tsafe_write(out, req_buf.buf, req_buf.len);\n+\t\tpacket_flush(out);\n+\t}\n \tstrbuf_release(&req_buf);\n \n \tif (use_sideband && cmds_sent) {\ndiff --git a/send-pack.h b/send-pack.h\nindex 05d7ab1..754943e 100644\n--- a/send-pack.h\n+++ b/send-pack.h\n@@ -11,6 +11,7 @@ struct send_pack_args {\n \t\tuse_thin_pack:1,\n \t\tuse_ofs_delta:1,\n \t\tdry_run:1,\n+\t\tsigned_push:1,\n \t\tstateless_rpc:1;\n };\n \ndiff --git a/transport.c b/transport.c\nindex fa279d5..7a7ffe4 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -476,6 +476,9 @@ static int set_git_option(struct git_transport_options *opts,\n \t\telse\n \t\t\topts->depth = atoi(value);\n \t\treturn 0;\n+\t} else if (!strcmp(name, TRANS_OPT_SIGNED_PUSH)) {\n+\t\topts->signed_push = !!value;\n+\t\treturn 0;\n \t}\n \treturn 1;\n }\n@@ -793,6 +796,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re\n \targs.progress = transport->progress;\n \targs.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);\n \targs.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN);\n+\targs.signed_push = !!(flags & TRANSPORT_PUSH_SIGNED);\n \n \tret = send_pack(&args, data->fd, data->conn, remote_refs,\n \t\t\t&data->extra_have);\ndiff --git a/transport.h b/transport.h\nindex 059b330..d2fa478 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -8,6 +8,7 @@ struct git_transport_options {\n \tunsigned thin : 1;\n \tunsigned keep : 1;\n \tunsigned followtags : 1;\n+\tunsigned signed_push : 1;\n \tint depth;\n \tconst char *uploadpack;\n \tconst char *receivepack;\n@@ -102,6 +103,7 @@ struct transport {\n #define TRANSPORT_PUSH_PORCELAIN 16\n #define TRANSPORT_PUSH_SET_UPSTREAM 32\n #define TRANSPORT_RECURSE_SUBMODULES_CHECK 64\n+#define TRANSPORT_PUSH_SIGNED 128\n \n #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)\n \n@@ -128,6 +130,8 @@ struct transport *transport_get(struct remote *, const char *);\n /* Aggressively fetch annotated tags if possible */\n #define TRANS_OPT_FOLLOWTAGS \"followtags\"\n \n+#define TRANS_OPT_SIGNED_PUSH \"signedpush\"\n+\n /**\n  * Returns 0 if the option was used, non-zero otherwise. Prints a\n  * message to stderr if the option is not used.\n-- \n1.7.7.rc0.188.g3793ac\n"},{"id":"175110","messageId":"1315512102-19022-5-git-send-email-gitster@pobox.com","threadId":"28338","inReplyTo":"1315512102-19022-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 4/7] push -s: send signed push certificate","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-09-08T20:01:39Z","receivedAt":"2011-09-08T20:01:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"And this uses the GPG interface to sign the push certificate. The format\nof the signed certificate is very similar to a signed tag, in that the\nresult is a concatenation of the payload, immediately followed by a\ndetached signature.\n\nThis places the same constraint as an annotated tag on the push\ncertificate payload; it has to be a text file and the final line\nmust not be an incomplete line.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/send-pack.c |   29 ++++++++++++-----------------\n 1 files changed, 12 insertions(+), 17 deletions(-)\n\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex 7f4778c..f715324 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -8,6 +8,7 @@\n #include \"send-pack.h\"\n #include \"quote.h\"\n #include \"transport.h\"\n+#include \"gpg-interface.h\"\n \n static const char send_pack_usage[] =\n \"git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\\n\"\n@@ -237,25 +238,18 @@ static int sideband_demux(int in, int out, void *data)\n \treturn ret;\n }\n \n-static void sign_push_certificate(struct strbuf *cert)\n+/*\n+ * Take the contents of cert->buf, and have the user GPG sign it, and\n+ * read it back in the strbuf.\n+ */\n+static int sign_push_certificate(struct strbuf *cert)\n {\n \t/*\n-\t * Here, take the contents of cert->buf, and have the user GPG\n-\t * sign it, and read it back in the strbuf.\n-\t *\n-\t * You may want to append some extra info to cert before giving\n-\t * it to GPG, possibly via a hook.\n-\t *\n-\t * Here we upcase them just to demonstrate that the codepath\n-\t * is being exercised.\n+\t * You may want to append some extra info to cert before\n+\t * giving it to GPG, possibly via a hook, here.\n \t */\n-\tchar *cp;\n-\tfor (cp = cert->buf; *cp; cp++) {\n-\t\tint ch = *cp;\n-\t\tif ('a' <= ch && ch <= 'z')\n-\t\t\t*cp = toupper(ch);\n-\t}\n-\treturn;\n+\n+\treturn sign_buffer(cert, git_committer_info(IDENT_NO_DATE));\n }\n \n int send_pack(struct send_pack_args *args,\n@@ -369,7 +363,8 @@ int send_pack(struct send_pack_args *args,\n \tif (signed_push && cmds_sent) {\n \t\tchar *cp, *ep;\n \n-\t\tsign_push_certificate(&push_cert);\n+\t\tif (sign_push_certificate(&push_cert))\n+\t\t\treturn error(_(\"failed to sign push certificate\"));\n \t\tstrbuf_reset(&req_buf);\n \t\tfor (cp = push_cert.buf; *cp; cp = ep) {\n \t\t\tep = strchrnul(cp, '\\n');\n-- \n1.7.7.rc0.188.g3793ac\n"},{"id":"175105","messageId":"1315512102-19022-6-git-send-email-gitster@pobox.com","threadId":"28338","inReplyTo":"1315512102-19022-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 5/7] push -s: receiving end","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-09-08T20:01:40Z","receivedAt":"2011-09-08T20:01:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This stores the GPG signed push certificate in the receiving repository\nusing the notes mechanism. The certificate is appended to a note in the\nrefs/notes/signed-push tree for each object that appears on the right\nhand side of the push certificate, i.e. the object that was pushed to\nupdate the tip of a ref.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/receive-pack.c |   72 +++++++++++++++++++++++++++++++++++++++++++----\n 1 files changed, 65 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 20b6799..344660e 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -11,6 +11,11 @@\n #include \"transport.h\"\n #include \"string-list.h\"\n #include \"sha1-array.h\"\n+#include \"gpg-interface.h\"\n+#include \"notes.h\"\n+#include \"notes-merge.h\"\n+#include \"blob.h\"\n+#include \"tag.h\"\n \n static const char receive_pack_usage[] = \"git receive-pack <git-dir>\";\n \n@@ -581,6 +586,22 @@ static void check_aliased_updates(struct command *commands)\n \tstring_list_clear(&ref_list, 0);\n }\n \n+static void get_note_text(struct strbuf *buf, struct notes_tree *t,\n+\t\t\t  const unsigned char *object)\n+{\n+\tconst unsigned char *sha1 = get_note(t, object);\n+\tchar *text;\n+\tunsigned long len;\n+\tenum object_type type;\n+\n+\tif (!sha1)\n+\t\treturn;\n+\ttext = read_sha1_file(sha1, &type, &len);\n+\tif (text && len && type == OBJ_BLOB)\n+\t\tstrbuf_add(buf, text, len);\n+\tfree(text);\n+}\n+\n static int record_signed_push(char *cert)\n {\n \t/*\n@@ -592,18 +613,55 @@ static int record_signed_push(char *cert)\n \t * You could also feed the signed push certificate to GPG,\n \t * verify the signer identity, and all the other fun stuff,\n \t * including feeding it to \"pre-receive-signature\" hook.\n-\t *\n-\t * Here we just throw it to stderr to demonstrate that the\n-\t * codepath is being exercised.\n \t */\n+\tsize_t total, payload;\n \tchar *cp, *ep;\n-\tfor (cp = cert; *cp; cp = ep) {\n+\tint ret = 0;\n+\tstruct notes_tree *t;\n+\tstruct strbuf nbuf = STRBUF_INIT;\n+\n+\tif (!cert)\n+\t\treturn 0;\n+\n+\tinit_notes(NULL, \"refs/notes/signed-push\", NULL, 0);\n+\tt = &default_notes_tree;\n+\n+\ttotal = strlen(cert);\n+\tpayload = parse_signature(cert, total);\n+\tfor (cp = cert; cp < cert + payload; cp = ep) {\n+\t\tunsigned char sha1[20], nsha1[20];\n+\n \t\tep = strchrnul(cp, '\\n');\n \t\tif (*ep == '\\n')\n \t\t\tep++;\n-\t\tfprintf(stderr, \"RSP: %.*s\", (int)(ep - cp), cp);\n+\t\tif (prefixcmp(cp, \"Update: \"))\n+\t\t\tcontinue;\n+\t\tcp += strlen(\"Update: \");\n+\t\tif (get_sha1_hex(cp, sha1) || cp[40] != ' ')\n+\t\t\tcontinue;\n+\n+\t\tget_note_text(&nbuf, t, sha1);\n+\t\tif (nbuf.len)\n+\t\t\tstrbuf_addch(&nbuf, '\\n');\n+\t\tstrbuf_add(&nbuf, cert, total);\n+\t\tif (write_sha1_file(nbuf.buf, nbuf.len, blob_type, nsha1) ||\n+\t\t    add_note(t, sha1, nsha1, NULL))\n+\t\t\tret = error(_(\"unable to write note object\"));\n+\t\tstrbuf_reset(&nbuf);\n \t}\n-\treturn 0;\n+\n+\tif (!ret) {\n+\t\tunsigned char commit[20];\n+\t\tunsigned char parent[20];\n+\t\tstruct ref_lock *lock;\n+\n+\t\tresolve_ref(t->ref, parent, 0, NULL);\n+\t\tlock = lock_any_ref_for_update(t->ref, parent, 0);\n+\t\tcreate_notes_commit(t, NULL, \"push\", commit);\n+\t\tret = write_ref_sha1(lock, commit, \"push\");\n+\t}\n+\tfree_notes(t);\n+\treturn ret;\n }\n \n static void execute_commands(struct command *commands, const char *unpacker_error)\n@@ -623,7 +681,7 @@ static void execute_commands(struct command *commands, const char *unpacker_erro\n \t\treturn;\n \t}\n \n-\tif (push_certificate && record_signed_push(push_certificate)) {\n+\tif (record_signed_push(push_certificate)) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next)\n \t\t\tcmd->error_string = \"n/a (push signature error)\";\n \t\treturn;\n-- \n1.7.7.rc0.188.g3793ac\n"},{"id":"175106","messageId":"1315512102-19022-7-git-send-email-gitster@pobox.com","threadId":"28338","inReplyTo":"1315512102-19022-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 6/7] refactor run_receive_hook()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-09-08T20:01:41Z","receivedAt":"2011-09-08T20:01:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Running a hook has to make complex set-up to establish web of\ncommunication between child process and multiplexer, which is common\nregardless of what kind of data is fed to the hook. Refactor the parts\nthat is specific to the data fed to the particular set of hooks from the\npart that runs the hook, so that the code can be reused to drive hooks\nthat take different kind of data.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/receive-pack.c |   71 +++++++++++++++++++++++++++++++++++-------------\n 1 files changed, 52 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 344660e..939b867 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -212,21 +212,15 @@ static int copy_to_sideband(int in, int out, void *arg)\n \treturn 0;\n }\n \n-static int run_receive_hook(struct command *commands, const char *hook_name)\n+typedef int (*feed_fn)(void *, const char **, size_t *);\n+static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_state)\n {\n-\tstatic char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4];\n-\tstruct command *cmd;\n \tstruct child_process proc;\n \tstruct async muxer;\n \tconst char *argv[2];\n-\tint have_input = 0, code;\n-\n-\tfor (cmd = commands; !have_input && cmd; cmd = cmd->next) {\n-\t\tif (!cmd->error_string)\n-\t\t\thave_input = 1;\n-\t}\n+\tint code;\n \n-\tif (!have_input || access(hook_name, X_OK) < 0)\n+\tif (access(hook_name, X_OK) < 0)\n \t\treturn 0;\n \n \targv[0] = hook_name;\n@@ -254,15 +248,13 @@ static int run_receive_hook(struct command *commands, const char *hook_name)\n \t\treturn code;\n \t}\n \n-\tfor (cmd = commands; cmd; cmd = cmd->next) {\n-\t\tif (!cmd->error_string) {\n-\t\t\tsize_t n = snprintf(buf, sizeof(buf), \"%s %s %s\\n\",\n-\t\t\t\tsha1_to_hex(cmd->old_sha1),\n-\t\t\t\tsha1_to_hex(cmd->new_sha1),\n-\t\t\t\tcmd->ref_name);\n-\t\t\tif (write_in_full(proc.in, buf, n) != n)\n-\t\t\t\tbreak;\n-\t\t}\n+\twhile (1) {\n+\t\tconst char *buf;\n+\t\tsize_t n;\n+\t\tif (feed(feed_state, &buf, &n))\n+\t\t\tbreak;\n+\t\tif (write_in_full(proc.in, buf, n) != n)\n+\t\t\tbreak;\n \t}\n \tclose(proc.in);\n \tif (use_sideband)\n@@ -270,6 +262,47 @@ static int run_receive_hook(struct command *commands, const char *hook_name)\n \treturn finish_command(&proc);\n }\n \n+struct receive_hook_feed_state {\n+\tstruct command *cmd;\n+\tstruct strbuf buf;\n+};\n+\n+static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)\n+{\n+\tstruct receive_hook_feed_state *state = state_;\n+\tstruct command *cmd = state->cmd;\n+\n+\twhile (cmd && cmd->error_string)\n+\t\tcmd = cmd->next;\n+\tif (!cmd)\n+\t\treturn -1; /* EOF */\n+\tstrbuf_reset(&state->buf);\n+\tstrbuf_addf(&state->buf, \"%s %s %s\\n\",\n+\t\t    sha1_to_hex(cmd->old_sha1), sha1_to_hex(cmd->new_sha1),\n+\t\t    cmd->ref_name);\n+\tstate->cmd = cmd->next;\n+\tif (bufp) {\n+\t\t*bufp = state->buf.buf;\n+\t\t*sizep = state->buf.len;\n+\t}\n+\treturn 0;\n+}\n+\n+static int run_receive_hook(struct command *commands, const char *hook_name)\n+{\n+\tstruct receive_hook_feed_state state;\n+\tint status;\n+\n+\tstrbuf_init(&state.buf, 0);\n+\tstate.cmd = commands;\n+\tif (feed_receive_hook(&state, NULL, NULL))\n+\t\treturn 0;\n+\tstate.cmd = commands;\n+\tstatus = run_and_feed_hook(hook_name, feed_receive_hook, &state);\n+\tstrbuf_release(&state.buf);\n+\treturn status;\n+}\n+\n static int run_update_hook(struct command *cmd)\n {\n \tstatic const char update_hook[] = \"hooks/update\";\n-- \n1.7.7.rc0.188.g3793ac\n"},{"id":"175108","messageId":"1315512102-19022-8-git-send-email-gitster@pobox.com","threadId":"28338","inReplyTo":"1315512102-19022-1-git-send-email-gitster@pobox.com","subject":"[PATCH v2 7/7] push -s: support pre-receive-signature hook","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-09-08T20:01:42Z","receivedAt":"2011-09-08T20:01:42Z","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 builtin/receive-pack.c |   31 +++++++++++++++++++++++++++----\n 1 files changed, 27 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 939b867..b5a54e7 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -303,6 +303,27 @@ static int run_receive_hook(struct command *commands, const char *hook_name)\n \treturn status;\n }\n \n+static int feed_signature_hook(void *state_, const char **bufp, size_t *sizep)\n+{\n+\tconst char **cert_p = state_;\n+\n+\tif (!*cert_p)\n+\t\treturn -1; /* EOF */\n+\t*bufp = *cert_p;\n+\t*cert_p = NULL; /* just return once */\n+\t*sizep = strlen(*bufp);\n+\treturn 0;\n+}\n+\n+static int run_receive_signature_hook(const char *cert)\n+{\n+\tstatic const char hook[] = \"hooks/pre-receive-signature\";\n+\n+\tif (!cert)\n+\t\treturn 0;\n+\treturn run_and_feed_hook(hook, feed_signature_hook, &cert);\n+}\n+\n static int run_update_hook(struct command *cmd)\n {\n \tstatic const char update_hook[] = \"hooks/update\";\n@@ -642,10 +663,6 @@ static int record_signed_push(char *cert)\n \t * certificate, grab the commit object names the push updates\n \t * refs to, and append the certificate to the notes to these\n \t * commits.\n-\t *\n-\t * You could also feed the signed push certificate to GPG,\n-\t * verify the signer identity, and all the other fun stuff,\n-\t * including feeding it to \"pre-receive-signature\" hook.\n \t */\n \tsize_t total, payload;\n \tchar *cp, *ep;\n@@ -714,6 +731,12 @@ static void execute_commands(struct command *commands, const char *unpacker_erro\n \t\treturn;\n \t}\n \n+\tif (run_receive_signature_hook(push_certificate)) {\n+\t\tfor (cmd = commands; cmd; cmd = cmd->next)\n+\t\t\tcmd->error_string = \"n/a (pre-receive-signature hook declined)\";\n+\t\treturn;\n+\t}\n+\n \tif (record_signed_push(push_certificate)) {\n \t\tfor (cmd = commands; cmd; cmd = cmd->next)\n \t\t\tcmd->error_string = \"n/a (push signature error)\";\n-- \n1.7.7.rc0.188.g3793ac\n"},{"id":"175241","messageId":"1315600904-17032-1-git-send-email-gitster@pobox.com","threadId":"28338","inReplyTo":"1315512102-19022-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 0/4] Signed push","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-09-09T20:41:40Z","receivedAt":"2011-09-09T20:41:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This is based an alternative design we discussed on the list to prepare\nand push the notes tree on the sending side.\n\nJunio C Hamano (4):\n  send-pack: typofix error message\n  Split GPG interface into its own helper library\n  rename \"match_refs()\" to \"match_push_refs()\"\n  push -s: signed push\n\n Makefile             |    2 +\n builtin/push.c       |    1 +\n builtin/remote.c     |    4 +-\n builtin/send-pack.c  |    4 +-\n builtin/tag.c        |   60 +++---------------------\n builtin/verify-tag.c |   35 +------------\n gpg-interface.c      |   94 ++++++++++++++++++++++++++++++++++++\n gpg-interface.h      |   11 ++++\n http-push.c          |    4 +-\n notes.c              |   16 ++++++\n notes.h              |    2 +\n remote.c             |   13 +++--\n remote.h             |    4 +-\n transport.c          |  128 +++++++++++++++++++++++++++++++++++++++++++++++++-\n transport.h          |    5 ++\n 15 files changed, 283 insertions(+), 100 deletions(-)\n create mode 100644 gpg-interface.c\n create mode 100644 gpg-interface.h\n\n-- \n1.7.7.rc0.188.g3793ac\n"},{"id":"175239","messageId":"1315600904-17032-2-git-send-email-gitster@pobox.com","threadId":"28338","inReplyTo":"1315600904-17032-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 1/4] send-pack: typofix error message","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-09-09T20:41:41Z","receivedAt":"2011-09-09T20:41:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The message identifies the process as receive-pack when it cannot fork the\nsideband demultiplexer. We are actually a send-pack.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/send-pack.c |    2 +-\n 1 files changed, 1 insertions(+), 1 deletions(-)\n\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex c1f6ddd..87833f4 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -334,7 +334,7 @@ int send_pack(struct send_pack_args *args,\n \t\tdemux.data = fd;\n \t\tdemux.out = -1;\n \t\tif (start_async(&demux))\n-\t\t\tdie(\"receive-pack: unable to fork off sideband demultiplexer\");\n+\t\t\tdie(\"send-pack: unable to fork off sideband demultiplexer\");\n \t\tin = demux.out;\n \t}\n \n-- \n1.7.7.rc0.188.g3793ac\n"},{"id":"175242","messageId":"1315600904-17032-3-git-send-email-gitster@pobox.com","threadId":"28338","inReplyTo":"1315600904-17032-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 2/4] Split GPG interface into its own helper library","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-09-09T20:41:42Z","receivedAt":"2011-09-09T20:41:42Z","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\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Makefile             |    2 +\n builtin/tag.c        |   60 ++++----------------------------\n builtin/verify-tag.c |   35 ++-----------------\n gpg-interface.c      |   94 ++++++++++++++++++++++++++++++++++++++++++++++++++\n gpg-interface.h      |   11 ++++++\n 5 files changed, 117 insertions(+), 85 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..e9d36fa 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@@ -208,60 +209,13 @@ 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+\tconst char *key;\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+\tif (*signingkey)\n+\t\tkey = signingkey;\n+\telse\n+\t\tkey = git_committer_info(IDENT_ERROR_ON_NO_NAME|IDENT_NO_DATE);\n+\treturn sign_buffer(buffer, key);\n }\n \n static const char tag_template[] =\ndiff --git a/builtin/verify-tag.c b/builtin/verify-tag.c\nindex 3134766..8b4f742 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,12 @@ 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-\n-\tunlink_or_warn(path);\n-\n-\treturn ret;\n+\treturn verify_signed_buffer(buf, size, len);\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..b83cca1\n--- /dev/null\n+++ b/gpg-interface.c\n@@ -0,0 +1,94 @@\n+/*\n+ * Copyright (c) 2011, Google Inc.\n+ */\n+#include \"cache.h\"\n+#include \"run-command.h\"\n+#include \"strbuf.h\"\n+#include \"gpg-interface.h\"\n+#include \"sigchain.h\"\n+\n+int sign_buffer(struct strbuf *buffer, const char *signing_key)\n+{\n+\tstruct child_process gpg;\n+\tconst char *args[4];\n+\tssize_t len;\n+\tint i, j;\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+\tlen = strbuf_read(buffer, 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 = 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+}\n+\n+int verify_signed_buffer(const char *buf, size_t total, size_t payload)\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, buf, total) < 0)\n+\t\treturn error(\"failed writing temporary file '%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+\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, payload);\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..7689357\n--- /dev/null\n+++ b/gpg-interface.h\n@@ -0,0 +1,11 @@\n+#ifndef GPG_INTERFACE_H\n+#define GPG_INTERFACE_H\n+\n+/*\n+ * Copyright (c) 2011, Google Inc.\n+ */\n+\n+extern int sign_buffer(struct strbuf *buffer, const char *signing_key);\n+extern int verify_signed_buffer(const char *buffer, size_t total, size_t payload);\n+\n+#endif\n-- \n1.7.7.rc0.188.g3793ac\n"},{"id":"175240","messageId":"1315600904-17032-4-git-send-email-gitster@pobox.com","threadId":"28338","inReplyTo":"1315600904-17032-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 3/4] rename \"match_refs()\" to \"match_push_refs()\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-09-09T20:41:43Z","receivedAt":"2011-09-09T20:41:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Yes, there is a warning that says the function is only used by push in big\nred letters in front of this function, but it didn't say a more important\nthing it should have said: what the function is for and what it does.\n\nRename it and document it to avoid future confusion.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/remote.c    |    4 ++--\n builtin/send-pack.c |    2 +-\n http-push.c         |    4 ++--\n remote.c            |   13 ++++++++-----\n remote.h            |    4 ++--\n transport.c         |    4 ++--\n 6 files changed, 17 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex f2a9c26..f16b544 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -389,8 +389,8 @@ static int get_push_ref_states(const struct ref *remote_refs,\n \tlocal_refs = get_local_heads();\n \tpush_map = copy_ref_list(remote_refs);\n \n-\tmatch_refs(local_refs, &push_map, remote->push_refspec_nr,\n-\t\t   remote->push_refspec, MATCH_REFS_NONE);\n+\tmatch_push_refs(local_refs, &push_map, remote->push_refspec_nr,\n+\t\t\tremote->push_refspec, MATCH_REFS_NONE);\n \n \tstates->push.strdup_strings = 1;\n \tfor (ref = push_map; ref; ref = ref->next) {\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex 87833f4..e0b8030 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -509,7 +509,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)\n \t\tflags |= MATCH_REFS_MIRROR;\n \n \t/* match them up */\n-\tif (match_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags))\n+\tif (match_push_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags))\n \t\treturn -1;\n \n \tset_ref_status_for_push(remote_refs, args.send_mirror,\ndiff --git a/http-push.c b/http-push.c\nindex 376331a..02f46a5 100644\n--- a/http-push.c\n+++ b/http-push.c\n@@ -1877,8 +1877,8 @@ int main(int argc, char **argv)\n \t}\n \n \t/* match them up */\n-\tif (match_refs(local_refs, &remote_refs,\n-\t\t       nr_refspec, (const char **) refspec, push_all)) {\n+\tif (match_push_refs(local_refs, &remote_refs,\n+\t\t\t    nr_refspec, (const char **) refspec, push_all)) {\n \t\trc = -1;\n \t\tgoto cleanup;\n \t}\ndiff --git a/remote.c b/remote.c\nindex b8ecfa5..536ffa3 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -1170,12 +1170,15 @@ static struct ref **tail_ref(struct ref **head)\n }\n \n /*\n- * Note. This is used only by \"push\"; refspec matching rules for\n- * push and fetch are subtly different, so do not try to reuse it\n- * without thinking.\n+ * Given the set of refs the local repository has, the set of refs the\n+ * remote repository has, and the refspec used for push, determine\n+ * what remote refs we will update and with what value by setting\n+ * peer_ref (which object is being pushed) and force (if the push is\n+ * forced) in elements of \"dst\". The function may add new elements to\n+ * dst (e.g. pushing to a new branch, done in match_explicit_refs).\n  */\n-int match_refs(struct ref *src, struct ref **dst,\n-\t       int nr_refspec, const char **refspec, int flags)\n+int match_push_refs(struct ref *src, struct ref **dst,\n+\t\t    int nr_refspec, const char **refspec, int flags)\n {\n \tstruct refspec *rs;\n \tint send_all = flags & MATCH_REFS_ALL;\ndiff --git a/remote.h b/remote.h\nindex 9a30a9d..6729477 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -96,8 +96,8 @@ void free_refspec(int nr_refspec, struct refspec *refspec);\n char *apply_refspecs(struct refspec *refspecs, int nr_refspec,\n \t\t     const char *name);\n \n-int match_refs(struct ref *src, struct ref **dst,\n-\t       int nr_refspec, const char **refspec, int all);\n+int match_push_refs(struct ref *src, struct ref **dst,\n+\t\t    int nr_refspec, const char **refspec, int all);\n void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,\n \tint force_update);\n \ndiff --git a/transport.c b/transport.c\nindex fa279d5..740a739 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -1033,8 +1033,8 @@ int transport_push(struct transport *transport,\n \t\tif (flags & TRANSPORT_PUSH_MIRROR)\n \t\t\tmatch_flags |= MATCH_REFS_MIRROR;\n \n-\t\tif (match_refs(local_refs, &remote_refs,\n-\t\t\t       refspec_nr, refspec, match_flags)) {\n+\t\tif (match_push_refs(local_refs, &remote_refs,\n+\t\t\t\t    refspec_nr, refspec, match_flags)) {\n \t\t\treturn -1;\n \t\t}\n \n-- \n1.7.7.rc0.188.g3793ac\n"},{"id":"175243","messageId":"1315600904-17032-5-git-send-email-gitster@pobox.com","threadId":"28338","inReplyTo":"1315600904-17032-1-git-send-email-gitster@pobox.com","subject":"[PATCH v3 4/4] push -s: signed push","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-09-09T20:41:44Z","receivedAt":"2011-09-09T20:41:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"If a tag is GPG-signed, and if you trust the cryptographic robustness of\nthe SHA-1 hash and GPG, you can sleep well knowing that all the history\nleading to the signed commit cannot be tampered with. However, it would be\nboth cumbersome and cluttering to sign each and every commit. Especially\nif you strive to keep your history clean by tweaking, rewriting and\npolishing your commits before pushing the resulting history out, many\ncommits you will create locally end up not mattering at all, and it is a\nwaste of time to sign them all.\n\nA better alternative could be to sign a \"push certificate\" (for the lack\nof better name) every time you push, asserting that what commits you are\npushing to update which refs.\n\nThe basic workflow based on this idea would go like this:\n\n 1. You push out your work with \"git push -s\";\n\n 2. \"git push\", as usual, learns where the remote refs are and which refs\n    are to be updated with this push. It prepares a text file in core,\n    that looks like the following:\n\n\tPush-Certificate-Version: 0\n\tPusher: Junio C Hamano <gitster@pobox.com> 1315427886 -0700\n\tUpdate: 3793ac56b4c4f9bf0bddc306a0cec21118683728 refs/heads/master\n\tUpdate: 12850bec0c24b529c9a9df6a95ad4bdeea39373e refs/heads/next\n\n    Each \"Update\" line shows the new object name at the tip of the ref\n    this push tries to update.\n\n    The user then is asked to sign this push certificate using GPG.\n\n 3. The signed push certificate is added as notes in the \"signed-push\"\n    notes tree to the objects listed in the certificate. The push refspec\n    is altered to push this notes tree to the other side.\n\nCompared to the alternative design posted earlier on the list, this does\nnot require changes in the receiving end, as the signed push certificate\nis added to the notes tree on the sending side. A possible downside is\nthat it may become more likely that a push is refused due to a conflict\nwhile updating the notes tree if the receiving repository is pushed into\nfrequently and by multiple people.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/push.c |    1 +\n notes.c        |   16 +++++++\n notes.h        |    2 +\n transport.c    |  124 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n transport.h    |    5 ++\n 5 files changed, 148 insertions(+), 0 deletions(-)\n\ndiff --git a/builtin/push.c b/builtin/push.c\nindex 35cce53..2238f4e 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -261,6 +261,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)\n \t\tOPT_BIT('u', \"set-upstream\", &flags, \"set upstream for git pull/status\",\n \t\t\tTRANSPORT_PUSH_SET_UPSTREAM),\n \t\tOPT_BOOLEAN(0, \"progress\", &progress, \"force progress reporting\"),\n+\t\tOPT_BIT('s', \"signed\", &flags, \"GPG sign the push\", TRANSPORT_PUSH_SIGNED),\n \t\tOPT_END()\n \t};\n \ndiff --git a/notes.c b/notes.c\nindex 93e9868..d081e7c 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -1296,3 +1296,19 @@ void expand_notes_ref(struct strbuf *sb)\n \telse\n \t\tstrbuf_insert(sb, 0, \"refs/notes/\", 11);\n }\n+\n+void get_note_text(struct strbuf *buf, struct notes_tree *t,\n+\t\t   const unsigned char *object)\n+{\n+\tconst unsigned char *sha1 = get_note(t, object);\n+\tchar *text;\n+\tunsigned long len;\n+\tenum object_type type;\n+\n+\tif (!sha1)\n+\t\treturn;\n+\ttext = read_sha1_file(sha1, &type, &len);\n+\tif (text && len && type == OBJ_BLOB)\n+\t\tstrbuf_add(buf, text, len);\n+\tfree(text);\n+}\ndiff --git a/notes.h b/notes.h\nindex c716694..5141e13 100644\n--- a/notes.h\n+++ b/notes.h\n@@ -312,4 +312,6 @@ void string_list_add_refs_from_colon_sep(struct string_list *list,\n /* Expand inplace a note ref like \"foo\" or \"notes/foo\" into \"refs/notes/foo\" */\n void expand_notes_ref(struct strbuf *sb);\n \n+void get_note_text(struct strbuf *, struct notes_tree *, const unsigned char *);\n+\n #endif\ndiff --git a/transport.c b/transport.c\nindex 740a739..de61669 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -11,6 +11,12 @@\n #include \"branch.h\"\n #include \"url.h\"\n #include \"submodule.h\"\n+#include \"gpg-interface.h\"\n+#include \"commit.h\"\n+#include \"notes.h\"\n+#include \"notes-merge.h\"\n+#include \"blob.h\"\n+#include \"tag.h\"\n \n /* rsync support */\n \n@@ -476,6 +482,9 @@ static int set_git_option(struct git_transport_options *opts,\n \t\telse\n \t\t\topts->depth = atoi(value);\n \t\treturn 0;\n+\t} else if (!strcmp(name, TRANS_OPT_SIGNED_PUSH)) {\n+\t\topts->signed_push = !!value;\n+\t\treturn 0;\n \t}\n \treturn 1;\n }\n@@ -1004,6 +1013,112 @@ void transport_set_verbosity(struct transport *transport, int verbosity,\n \ttransport->progress = force_progress || (verbosity >= 0 && isatty(2));\n }\n \n+static int is_ref_pushed(const struct ref *ref)\n+{\n+\tif (!ref->peer_ref || ref->deletion)\n+\t\treturn 0;\n+\n+\t/* Filter out unchanged ones */\n+\tswitch (ref->status) {\n+\tcase REF_STATUS_REJECT_NONFASTFORWARD:\n+\tcase REF_STATUS_UPTODATE:\n+\t\treturn 0;\n+\tdefault:\n+\t\t; /* ok */\n+\t}\n+\n+\treturn 1;\n+}\n+\n+static const char push_signature_note[] = \"refs/notes/signed-push\";\n+\n+static int add_push_signature_note(struct ref *signature_note,\n+\t\t\t\t   struct ref *ref,\n+\t\t\t\t   struct strbuf *cert)\n+{\n+\tstruct notes_tree *notes_tree;\n+\tstruct strbuf nbuf = STRBUF_INIT;\n+\tint ret = 0;\n+\tunsigned char parent[20], commit[20];\n+\tstruct ref_lock *lock;\n+\n+\tinit_notes(NULL, push_signature_note, NULL, 0);\n+\tnotes_tree = &default_notes_tree;\n+\n+\tresolve_ref(notes_tree->ref, parent, 0, NULL);\n+\tlock = lock_any_ref_for_update(notes_tree->ref, parent, 0);\n+\n+\tfor ( ; ref; ref = ref->next) {\n+\t\tunsigned char nsha1[20];\n+\n+\t\tif ((ref == signature_note) || !is_ref_pushed(ref))\n+\t\t\tcontinue;\n+\t\tget_note_text(&nbuf, notes_tree, ref->new_sha1);\n+\t\tif (nbuf.len)\n+\t\t\tstrbuf_addch(&nbuf, '\\n');\n+\t\tstrbuf_add(&nbuf, cert->buf, cert->len);\n+\t\tif (write_sha1_file(nbuf.buf, nbuf.len, blob_type, nsha1) ||\n+\t\t    add_note(notes_tree, ref->new_sha1, nsha1, NULL))\n+\t\t\tret = error(_(\"unable to write note object\"));\n+\t\tstrbuf_reset(&nbuf);\n+\t}\n+\n+\tif (!ret) {\n+\t\tcreate_notes_commit(notes_tree, NULL, \"push\", commit);\n+\t\tret = write_ref_sha1(lock, commit, \"signed push\");\n+\t}\n+\tfree_notes(notes_tree);\n+\n+\tif (!ret) {\n+\t\thashcpy(signature_note->new_sha1, commit);\n+\t\tif (!signature_note->peer_ref)\n+\t\t\tsignature_note->peer_ref = alloc_ref(push_signature_note);\n+\t}\n+\treturn ret;\n+}\n+\n+static int sign_push_certificate(struct strbuf *cert)\n+{\n+\treturn sign_buffer(cert, git_committer_info(IDENT_NO_DATE));\n+}\n+\n+static int sign_push(struct transport *transport,\n+\t\t     struct ref *remote_refs,\n+\t\t     int flags)\n+{\n+\tstruct ref *ref, *tail = NULL, *signature_note = NULL;\n+\tstruct strbuf push_cert = STRBUF_INIT;\n+\tint updates = 0, ret = 0;\n+\n+\tif (flags & TRANSPORT_PUSH_DRY_RUN)\n+\t\treturn 0;\n+\n+\tstrbuf_addstr(&push_cert, \"Push-Certificate-Version: 0\\n\");\n+\tstrbuf_addf(&push_cert, \"Pusher: %s\\n\", git_committer_info(0));\n+\n+\tfor (ref = remote_refs; ref; ref = ref->next) {\n+\t\ttail = ref;\n+\t\tif (!strcmp(ref->name, push_signature_note))\n+\t\t\tsignature_note = ref;\n+\t\tif (!is_ref_pushed(ref))\n+\t\t\tcontinue;\n+\t\tupdates++;\n+\t\tstrbuf_addf(&push_cert, \"Update: %s %s\\n\",\n+\t\t\t    sha1_to_hex(ref->new_sha1), ref->name);\n+\t}\n+\n+\tif (updates && !sign_push_certificate(&push_cert)) {\n+\t\tif (!signature_note) {\n+\t\t\tsignature_note = alloc_ref(push_signature_note);\n+\t\t\ttail->next = signature_note;\n+\t\t}\n+\t\tret = add_push_signature_note(signature_note,\n+\t\t\t\t\t      remote_refs, &push_cert);\n+\t}\n+\tstrbuf_release(&push_cert);\n+\treturn ret;\n+}\n+\n int transport_push(struct transport *transport,\n \t\t   int refspec_nr, const char **refspec, int flags,\n \t\t   int *nonfastforward)\n@@ -1015,6 +1130,9 @@ int transport_push(struct transport *transport,\n \t\t/* Maybe FIXME. But no important transport uses this case. */\n \t\tif (flags & TRANSPORT_PUSH_SET_UPSTREAM)\n \t\t\tdie(\"This transport does not support using --set-upstream\");\n+\t\t/* Likewise */\n+\t\tif (flags & TRANSPORT_PUSH_SIGNED)\n+\t\t\tdie(\"This transport does not support using --signed\");\n \n \t\treturn transport->push(transport, refspec_nr, refspec, flags);\n \t} else if (transport->push_refs) {\n@@ -1050,7 +1168,13 @@ int transport_push(struct transport *transport,\n \t\t\t\t\tdie(\"There are unpushed submodules, aborting.\");\n \t\t}\n \n+\t\tif (flags & TRANSPORT_PUSH_SIGNED) {\n+\t\t\tif (sign_push(transport, remote_refs, flags))\n+\t\t\t\treturn -1;\n+\t\t}\n+\n \t\tpush_ret = transport->push_refs(transport, remote_refs, flags);\n+\n \t\terr = push_had_errors(remote_refs);\n \t\tret = push_ret | err;\n \ndiff --git a/transport.h b/transport.h\nindex 059b330..034ff5a 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -8,6 +8,7 @@ struct git_transport_options {\n \tunsigned thin : 1;\n \tunsigned keep : 1;\n \tunsigned followtags : 1;\n+\tunsigned signed_push : 1;\n \tint depth;\n \tconst char *uploadpack;\n \tconst char *receivepack;\n@@ -102,6 +103,7 @@ struct transport {\n #define TRANSPORT_PUSH_PORCELAIN 16\n #define TRANSPORT_PUSH_SET_UPSTREAM 32\n #define TRANSPORT_RECURSE_SUBMODULES_CHECK 64\n+#define TRANSPORT_PUSH_SIGNED 128\n \n #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)\n \n@@ -128,6 +130,9 @@ struct transport *transport_get(struct remote *, const char *);\n /* Aggressively fetch annotated tags if possible */\n #define TRANS_OPT_FOLLOWTAGS \"followtags\"\n \n+/* Signed push */\n+#define TRANS_OPT_SIGNED_PUSH \"signedpush\"\n+\n /**\n  * Returns 0 if the option was used, non-zero otherwise. Prints a\n  * message to stderr if the option is not used.\n-- \n1.7.7.rc0.188.g3793ac\n"},{"id":"175247","messageId":"7v62l1to11.fsf_-_@alter.siamese.dyndns.org","threadId":"28338","inReplyTo":"1315600904-17032-5-git-send-email-gitster@pobox.com","subject":"[PATCH v3.1 4/4] push -s: signed push","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-09-09T21:16:10Z","receivedAt":"2011-09-09T21:16:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The earlier one had some unnecessary bits specific to git transport copied\nfrom the v2 patch, but this alternative design is transport agnostic, so\nhere is a replacemnt to remove them.\n\n-- >8 --\nSubject: [PATCH] push -s: signed push\n\nIf a tag is GPG-signed, and if you trust the cryptographic robustness of\nthe SHA-1 hash and GPG, you can sleep well knowing that all the history\nleading to the signed commit cannot be tampered with. However, it would be\nboth cumbersome and cluttering to sign each and every commit. Especially\nif you strive to keep your history clean by tweaking, rewriting and\npolishing your commits before pushing the resulting history out, many\ncommits you will create locally end up not mattering at all, and it is a\nwaste of time to sign them all.\n\nA better alternative could be to sign a \"push certificate\" (for the lack\nof better name) every time you push, asserting that what commits you are\npushing to update which refs.\n\nThe basic workflow based on this idea would go like this:\n\n 1. You push out your work with \"git push -s\";\n\n 2. \"git push\", as usual, learns where the remote refs are and which refs\n    are to be updated with this push. It prepares a text file in core,\n    that looks like the following:\n\n\tPush-Certificate-Version: 0\n\tPusher: Junio C Hamano <gitster@pobox.com> 1315427886 -0700\n\tUpdate: 3793ac56b4c4f9bf0bddc306a0cec21118683728 refs/heads/master\n\tUpdate: 12850bec0c24b529c9a9df6a95ad4bdeea39373e refs/heads/next\n\n    Each \"Update\" line shows the new object name at the tip of the ref\n    this push tries to update.\n\n    The user then is asked to sign this push certificate using GPG.\n\n 3. The signed push certificate is added as notes in the \"signed-push\"\n    notes tree to the objects listed in the certificate. The push refspec\n    is altered to push this notes tree to the other side.\n\nCompared to the alternative design posted earlier on the list, this does\nnot require changes in the receiving end, as the signed push certificate\nis added to the notes tree on the sending side. A possible downside is\nthat it may become more likely that a push is refused due to a conflict\nwhile updating the notes tree if the receiving repository is pushed into\nfrequently and by multiple people.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/push.c |    1 +\n notes.c        |   16 +++++++\n notes.h        |    2 +\n transport.c    |  120 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n transport.h    |    2 +\n 5 files changed, 141 insertions(+), 0 deletions(-)\n\ndiff --git a/builtin/push.c b/builtin/push.c\nindex 35cce53..2238f4e 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -261,6 +261,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)\n \t\tOPT_BIT('u', \"set-upstream\", &flags, \"set upstream for git pull/status\",\n \t\t\tTRANSPORT_PUSH_SET_UPSTREAM),\n \t\tOPT_BOOLEAN(0, \"progress\", &progress, \"force progress reporting\"),\n+\t\tOPT_BIT('s', \"signed\", &flags, \"GPG sign the push\", TRANSPORT_PUSH_SIGNED),\n \t\tOPT_END()\n \t};\n \ndiff --git a/notes.c b/notes.c\nindex 93e9868..d081e7c 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -1296,3 +1296,19 @@ void expand_notes_ref(struct strbuf *sb)\n \telse\n \t\tstrbuf_insert(sb, 0, \"refs/notes/\", 11);\n }\n+\n+void get_note_text(struct strbuf *buf, struct notes_tree *t,\n+\t\t   const unsigned char *object)\n+{\n+\tconst unsigned char *sha1 = get_note(t, object);\n+\tchar *text;\n+\tunsigned long len;\n+\tenum object_type type;\n+\n+\tif (!sha1)\n+\t\treturn;\n+\ttext = read_sha1_file(sha1, &type, &len);\n+\tif (text && len && type == OBJ_BLOB)\n+\t\tstrbuf_add(buf, text, len);\n+\tfree(text);\n+}\ndiff --git a/notes.h b/notes.h\nindex c716694..5141e13 100644\n--- a/notes.h\n+++ b/notes.h\n@@ -312,4 +312,6 @@ void string_list_add_refs_from_colon_sep(struct string_list *list,\n /* Expand inplace a note ref like \"foo\" or \"notes/foo\" into \"refs/notes/foo\" */\n void expand_notes_ref(struct strbuf *sb);\n \n+void get_note_text(struct strbuf *, struct notes_tree *, const unsigned char *);\n+\n #endif\ndiff --git a/transport.c b/transport.c\nindex 740a739..702d438 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -11,6 +11,12 @@\n #include \"branch.h\"\n #include \"url.h\"\n #include \"submodule.h\"\n+#include \"gpg-interface.h\"\n+#include \"commit.h\"\n+#include \"notes.h\"\n+#include \"notes-merge.h\"\n+#include \"blob.h\"\n+#include \"tag.h\"\n \n /* rsync support */\n \n@@ -1004,6 +1010,112 @@ void transport_set_verbosity(struct transport *transport, int verbosity,\n \ttransport->progress = force_progress || (verbosity >= 0 && isatty(2));\n }\n \n+static int is_ref_pushed(const struct ref *ref)\n+{\n+\tif (!ref->peer_ref || ref->deletion)\n+\t\treturn 0;\n+\n+\t/* Filter out unchanged ones */\n+\tswitch (ref->status) {\n+\tcase REF_STATUS_REJECT_NONFASTFORWARD:\n+\tcase REF_STATUS_UPTODATE:\n+\t\treturn 0;\n+\tdefault:\n+\t\t; /* ok */\n+\t}\n+\n+\treturn 1;\n+}\n+\n+static const char push_signature_note[] = \"refs/notes/signed-push\";\n+\n+static int add_push_signature_note(struct ref *signature_note,\n+\t\t\t\t   struct ref *ref,\n+\t\t\t\t   struct strbuf *cert)\n+{\n+\tstruct notes_tree *notes_tree;\n+\tstruct strbuf nbuf = STRBUF_INIT;\n+\tint ret = 0;\n+\tunsigned char parent[20], commit[20];\n+\tstruct ref_lock *lock;\n+\n+\tinit_notes(NULL, push_signature_note, NULL, 0);\n+\tnotes_tree = &default_notes_tree;\n+\n+\tresolve_ref(notes_tree->ref, parent, 0, NULL);\n+\tlock = lock_any_ref_for_update(notes_tree->ref, parent, 0);\n+\n+\tfor ( ; ref; ref = ref->next) {\n+\t\tunsigned char nsha1[20];\n+\n+\t\tif ((ref == signature_note) || !is_ref_pushed(ref))\n+\t\t\tcontinue;\n+\t\tget_note_text(&nbuf, notes_tree, ref->new_sha1);\n+\t\tif (nbuf.len)\n+\t\t\tstrbuf_addch(&nbuf, '\\n');\n+\t\tstrbuf_add(&nbuf, cert->buf, cert->len);\n+\t\tif (write_sha1_file(nbuf.buf, nbuf.len, blob_type, nsha1) ||\n+\t\t    add_note(notes_tree, ref->new_sha1, nsha1, NULL))\n+\t\t\tret = error(_(\"unable to write note object\"));\n+\t\tstrbuf_reset(&nbuf);\n+\t}\n+\n+\tif (!ret) {\n+\t\tcreate_notes_commit(notes_tree, NULL, \"push\", commit);\n+\t\tret = write_ref_sha1(lock, commit, \"signed push\");\n+\t}\n+\tfree_notes(notes_tree);\n+\n+\tif (!ret) {\n+\t\thashcpy(signature_note->new_sha1, commit);\n+\t\tif (!signature_note->peer_ref)\n+\t\t\tsignature_note->peer_ref = alloc_ref(push_signature_note);\n+\t}\n+\treturn ret;\n+}\n+\n+static int sign_push_certificate(struct strbuf *cert)\n+{\n+\treturn sign_buffer(cert, git_committer_info(IDENT_NO_DATE));\n+}\n+\n+static int sign_push(struct transport *transport,\n+\t\t     struct ref *remote_refs,\n+\t\t     int flags)\n+{\n+\tstruct ref *ref, *tail = NULL, *signature_note = NULL;\n+\tstruct strbuf push_cert = STRBUF_INIT;\n+\tint updates = 0, ret = 0;\n+\n+\tif (flags & TRANSPORT_PUSH_DRY_RUN)\n+\t\treturn 0;\n+\n+\tstrbuf_addstr(&push_cert, \"Push-Certificate-Version: 0\\n\");\n+\tstrbuf_addf(&push_cert, \"Pusher: %s\\n\", git_committer_info(0));\n+\n+\tfor (ref = remote_refs; ref; ref = ref->next) {\n+\t\ttail = ref;\n+\t\tif (!strcmp(ref->name, push_signature_note))\n+\t\t\tsignature_note = ref;\n+\t\tif (!is_ref_pushed(ref))\n+\t\t\tcontinue;\n+\t\tupdates++;\n+\t\tstrbuf_addf(&push_cert, \"Update: %s %s\\n\",\n+\t\t\t    sha1_to_hex(ref->new_sha1), ref->name);\n+\t}\n+\n+\tif (updates && !sign_push_certificate(&push_cert)) {\n+\t\tif (!signature_note) {\n+\t\t\tsignature_note = alloc_ref(push_signature_note);\n+\t\t\ttail->next = signature_note;\n+\t\t}\n+\t\tret = add_push_signature_note(signature_note,\n+\t\t\t\t\t      remote_refs, &push_cert);\n+\t}\n+\tstrbuf_release(&push_cert);\n+\treturn ret;\n+}\n+\n int transport_push(struct transport *transport,\n \t\t   int refspec_nr, const char **refspec, int flags,\n \t\t   int *nonfastforward)\n@@ -1015,6 +1127,9 @@ int transport_push(struct transport *transport,\n \t\t/* Maybe FIXME. But no important transport uses this case. */\n \t\tif (flags & TRANSPORT_PUSH_SET_UPSTREAM)\n \t\t\tdie(\"This transport does not support using --set-upstream\");\n+\t\t/* Likewise */\n+\t\tif (flags & TRANSPORT_PUSH_SIGNED)\n+\t\t\tdie(\"This transport does not support using --signed\");\n \n \t\treturn transport->push(transport, refspec_nr, refspec, flags);\n \t} else if (transport->push_refs) {\n@@ -1050,6 +1165,11 @@ int transport_push(struct transport *transport,\n \t\t\t\t\tdie(\"There are unpushed submodules, aborting.\");\n \t\t}\n \n+\t\tif (flags & TRANSPORT_PUSH_SIGNED) {\n+\t\t\tif (sign_push(transport, remote_refs, flags))\n+\t\t\t\treturn -1;\n+\t\t}\n+\n \t\tpush_ret = transport->push_refs(transport, remote_refs, flags);\n \t\terr = push_had_errors(remote_refs);\n \t\tret = push_ret | err;\ndiff --git a/transport.h b/transport.h\nindex 059b330..e525d07 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -8,6 +8,7 @@ struct git_transport_options {\n \tunsigned thin : 1;\n \tunsigned keep : 1;\n \tunsigned followtags : 1;\n+\tunsigned signed_push : 1;\n \tint depth;\n \tconst char *uploadpack;\n \tconst char *receivepack;\n@@ -102,6 +103,7 @@ struct transport {\n #define TRANSPORT_PUSH_PORCELAIN 16\n #define TRANSPORT_PUSH_SET_UPSTREAM 32\n #define TRANSPORT_RECURSE_SUBMODULES_CHECK 64\n+#define TRANSPORT_PUSH_SIGNED 128\n \n #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)\n \n-- \n1.7.7.rc0.188.g3793ac\n"},{"id":"175259","messageId":"7vipp1otyp.fsf@alter.siamese.dyndns.org","threadId":"28338","inReplyTo":"1315600904-17032-1-git-send-email-gitster@pobox.com","subject":"Re: [PATCH v3 0/4] Signed push","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-09-10T05:19:10Z","receivedAt":"2011-09-10T05:19:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Now that I have tried two concrete implementations and had a chance to\nplay with both ideas, even though I haven't started pushing to other\nrepositories with signature for real, I can make some observations with a\ncertain degree of confidence. I have to say that there is one thing I do\nnot really like about this.\n\nDuring my typical work day, I usually run at least one, but possibly up to\nthree to four integration cycles, and each cycle is concluded with these\nactions (in this order):\n\n - Push maint/master/next/pu integration branches and optionaly todo\n   branch to k.org;\n\n - Fetch html/man branches from k.org that are auto-updated as the result\n   of the above push, if necessary;\n\n - Push maint/master/next/pu/html/man branches and optionally todo branch\n   to repo.or.cz and code.google.com;\n\n - Push maint/master/html/man branches and optionally todo branch to\n   sourceforge.net and sourceforge.jp; and finally\n\n - Mirror-push everything to github.com.\n\nFirst let's make it clear what is _not_ what I do _not_ like.\n\nI do not mind having to unlock my GPG key for each of the above set of\npushes. After all, these four classes of repositories:\n\n (1) the primary public repository at k.org;\n (2) its two mirrors at repo and google;\n (3) the secondary partial mirrors at two sourceforge sites; and\n (4) full mirror at github\n\nreceive different sets of branches.  It is not just I do not mind, but I\nactively would prefer, making separate assertions to say \"These commits\nthis particular site has were placed here by me to these branches with\nthis push\", and I think it is preferrable that the assertions are\ncustomized for destination sites. So it is natural to generate separate\npush certificates to make separate assertions, and that is why unlocking\nthe key multiple times nor signing one document per push destination is\nnot a problem.\n\nNow, with that behind us, let's discuss what I do _not_ like.\n\nThe v3 design that prepares and pushes the signed-push notes tree locally\nhas one major downside that I did not anticipate before starting to really\nthink about using signed pushes.\n\nThink about what is contained in the signed-push notes tree that is pushed\nto sourceforge during one cycle of my work day described above. The site\ngets neither next nor pu branch, but because\n\n (1) I push out the same commit at the tip of master as I push to k.org,\n     repo and sourceforge;\n (2) the push to sourceforge happens after the pushes to k.org and repo\n     happens; and\n (3) in v3 design, I have to keep the notes tree prepared locally before\n     pushing things out,\n\nthe push certificate note attached to the commit object at the tip of the\n'master' branch becomes a concatenation of three pushes, and some of them\ndescribe the branches that sourceforge does not get (namely, next and pu\nthat I pushed out to k.org and repo).\n\nWorse yet, after I conclude this round by pushing all the topics to github\nwith signature, my signed-push notes history has push certificates that\ndescribe my previous push of _all topics_ to github repository, and on top\nof that notes tree, signed-push notes to describe the next push to k.org\nis built, and it is sent to k.org at the beginning of the next cycle.\n\nNow imagine a hypothetical universe where my k.org repository is the \"open\nsource Git\" (eh, that is not hypothetical), but some of the topic branches\nthat are not merged to pu were the \"proprietary extensions\" published only\nto the github repository. Further imagine that in this hypothetical\nuniverse, my github repository is not the \"open source\" one, but is a\nproprietary \"partner server\" I use to work with other \"Git proprietary\nextensions\" closed source commercial project.\n\nYou can easily understand why the behaviour of the natural consequence of\nthe v3 design is a horrible mistake. In such a set-up, even branch names\nmay contain sensitive information, yet we are exposing the record of\npushes to github to the shared signed-push notes tree at k.org during the\nnext round. The presense of such a partner server may even be a sensitive\npiece of information that shouldn't be devulged to the open source k.org\nrepository.\n\nThere is no such problem with the v2 design, where the reciever site is\nsolely responsible to keep the push certificate and does not force me to\nhave copies of all the push certificates locally to preserve ordering. The\nsigned-push notes for each hosting site contain _only_ push certificates\nthat pertain to pushes to _that_ site in the v2 design.\n\nAnd I think that really is the right way to do a signed push.\n\nThe primary motivation of the signed push is for me to assert \"These\ncommits this particular site has were placed here by me to these branches\nwith this push\", so that people can be sure that the commits near the tip\nof my branches since the latest tagged commits (which are signed by my GPG\nkey) did come from me. In order to achieve that goal, k.org repository has\nno business knowing what I pushed to github repository, and github\nrepository has no business knowing what I pushed to k.org repository.\n\nEven under v2 design, if somebody who has access to both k.org (public)\nand github (proprietary in the hypothetical universe) would want to\ncombine the signed-push notes to see a unified picture (perhaps I push to\nthese two sites with different frequencies), he can fetch signed-push\nnotes from both sites and merge them himself. But v3 design also allows\nanybody who has access to k.org (which is public so by definition that\ntruly is anybody) to peek into signed-push notes at k.org to learn more\nthan he should be able to.\n\nIt is cumbersome, if not impossible, to achieve the same separation v2\ndesign gives us by default. The pusher of course can maintain separate\nsigned-push branches per push destinations, but I think that is merely a\nworkaround.\n\nTo begin with, there is no real reason for _me_ to keep _any_ signed-push\nrecord for places _I_ pushed. The assertion I am making by signing my\npushes is for people who get commits beyond my last tagged commits to be\nsure that they do come from me, and while it is fine to have back-up\ncopies of them for your own use, there is no reason to _require_ me to do\nso. But the \"locally prepare notes tree and push that out along with what\nyou wanted to push out\" design does exactly that.\n"},{"id":"175277","messageId":"CAGdFq_hWVPCEeJKKccp4Wc-j+XMSFXqRf6VYd7ngLER8RhODRQ@mail.gmail.com","threadId":"28338","inReplyTo":"7vipp1otyp.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v3 0/4] Signed push","fromName":"Sverre Rabbelier","fromEmail":"srabbelier@gmail.com","sentAt":"2011-09-10T15:17:14Z","receivedAt":"2011-09-10T15:17:14Z","isPatch":true,"sender":{"key":"srabbelier@gmail.com","avatar":"https://avatars.githubusercontent.com/u/3098?v=4"},"body":"Heya,\n\nOn Sat, Sep 10, 2011 at 07:19, Junio C Hamano <gitster@pobox.com> wrote:\n> Even under v2 design, if somebody who has access to both k.org (public)\n> and github (proprietary in the hypothetical universe) would want to\n> combine the signed-push notes to see a unified picture (perhaps I push to\n> these two sites with different frequencies), he can fetch signed-push\n> notes from both sites and merge them himself. But v3 design also allows\n> anybody who has access to k.org (which is public so by definition that\n> truly is anybody) to peek into signed-push notes at k.org to learn more\n> than he should be able to.\n\nI think this is also some further motivation to have a\nrefs/remotes/github/notes/signed-push and a\nrefs/remotes/korg/notes/signed-push, rather than have everything\nautomatically go into refs/notes/signed-push when fetching from a\nremote.\n\n(If I misremembered and it's already that way, please ignore this message :P)\n\n-- \nCheers,\n\nSverre Rabbelier\n"},{"id":"175278","messageId":"7vehzopdga.fsf@alter.siamese.dyndns.org","threadId":"28338","inReplyTo":"CAGdFq_hWVPCEeJKKccp4Wc-j+XMSFXqRf6VYd7ngLER8RhODRQ@mail.gmail.com","subject":"Re: [PATCH v3 0/4] Signed push","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-09-10T16:30:29Z","receivedAt":"2011-09-10T16:30:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Sverre Rabbelier <srabbelier@gmail.com> writes:\n\n> I think this is also some further motivation to have a\n\nDid you miss that I already mentioned that workaround?  It does not _fix_\nthe fundamental breakage, which is that you are _forcing_ the sending side\nto keep copies, though.\n"},{"id":"175283","messageId":"20110910192225.GA5397@thunk.org","threadId":"28338","inReplyTo":"7vehzopdga.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v3 0/4] Signed push","fromName":"Ted Ts'o","fromEmail":"tytso@mit.edu","sentAt":"2011-09-10T19:22:25Z","receivedAt":"2011-09-10T19:22:25Z","isPatch":true,"sender":{"key":"tytso@mit.edu","avatar":"https://avatars.githubusercontent.com/u/51416?v=4"},"body":"On Sat, Sep 10, 2011 at 09:30:29AM -0700, Junio C Hamano wrote:\n> Sverre Rabbelier <srabbelier@gmail.com> writes:\n> \n> > I think this is also some further motivation to have a\n> \n> Did you miss that I already mentioned that workaround?  It does not _fix_\n> the fundamental breakage, which is that you are _forcing_ the sending side\n> to keep copies, though.\n\nI guess I'm confused about what the problem is with this?\n\nIf I do something like this:\n\ngit tag -s -m for_linus-20110910 for_linus-20110910\ngit push github\ngit push --tags github\n\nI'm \"forcing\" the sending side to keep the signed tag, no?  Isn't that\nkind of implicit in allowing someone to push to your repo?\n\n     \t\t    \t     \t     \t     \t- Ted\n"},{"id":"175282","messageId":"robbat2-20110910T193556-827326546Z@orbis-terrarum.net","threadId":"28338","inReplyTo":"7vehzopdga.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v3 0/4] Signed push","fromName":"Robin H. Johnson","fromEmail":"robbat2@gentoo.org","sentAt":"2011-09-10T20:05:07Z","receivedAt":"2011-09-10T20:05:07Z","isPatch":true,"sender":{"key":"robbat2@gentoo.org","avatar":"https://avatars.githubusercontent.com/u/373898?v=4"},"body":"On Sat, Sep 10, 2011 at 09:30:29AM -0700,  Junio C Hamano wrote:\n> Sverre Rabbelier <srabbelier@gmail.com> writes:\n> \n> > I think this is also some further motivation to have a\n> \n> Did you miss that I already mentioned that workaround?  It does not _fix_\n> the fundamental breakage, which is that you are _forcing_ the sending side\n> to keep copies, though.\nIn the case of a shared Git repo, I'd like to pull copies added by other\ndevelopers, so that I can verify the content locally as well. In that\ncase, I'd need to have copies of the certificates I created as well.\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":"175284","messageId":"7v4o0jq2fx.fsf@alter.siamese.dyndns.org","threadId":"28338","inReplyTo":"20110910192225.GA5397@thunk.org","subject":"Re: [PATCH v3 0/4] Signed push","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-09-11T01:42:58Z","receivedAt":"2011-09-11T01:42:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ted Ts'o <tytso@mit.edu> writes:\n\n> I guess I'm confused about what the problem is with this?\n\nYeah, I have to agree.\n\n> If I do something like this:\n>\n> git tag -s -m for_linus-20110910 for_linus-20110910\n> git push github\n> git push --tags github\n>\n> I'm \"forcing\" the sending side to keep the signed tag, no?\n\nNo, you are not forced to _keep_ it. After pushing you can delete it\nlocally.\n\nThe reason your \"tag\" example is fundamentally different is because a tag\nlike for_linus_20110910 is a one-shot thing and you can choose to remove\nit from your local namespace once you are done pushing. It does not affect\nyour ability to make another signed tag for_linus_20110911 before pushing\ntomorrow.\n\nThe point in this round of \"signed push\" topic is to allow people not tag\nevery time before they push, making it easier to sign their pushes to\nencourage it, so that other people can have a way to verify the commits\nnear the tip of branches that are not still tagged in between releases.\nInstead of contaminating refs/tags/ namespace with daily tags, the idea\nwas to keep a single \"signed-push\" notes tree on the receiving end (which\nis the distribution point for consumers) that contain the signed record of\npushes.\n\nThe original \"signed push\" (what I called v2) design was for the sender to\nprepare the record that goes into the notes tree, but record the notes\ntree at the receiving end (this does _not_ prevent the sender from\nfetching it back to keep his local copy, but the sender is _not_ required\nto do so). It needs updates to both sending and receiving end.\n\nAn alternative idea (which I implemented as v3) that came up during the\ndiscussion was to instead have the sender add this record locally to the\nsigned-push notes tree, and push it out along with the branches. For this\npush not to lose _existing_ records of pushes at the receiving end, the\npusher is required to have an up-to-date copy of signed-push notes tree,\nand add the new record to it before pushing it out. One upside is that\nthis does not need updates to receiving end.\n\nI do not know if you read the message Sverre was responding to, but the\n\"you have to have local copy\" requirement has another and potentially\nbigger downside (which Sverre did not quote) for people who push out to\nmultiple places.\n\nPerhaps we shouldn't worry about tag namespace contamination to make\nthings easier and simpler and stop using notes tree?\n"},{"id":"175288","messageId":"CAGdFq_gzmkzGrWeSfZwPNWWSkXBsTm+e52J6GxEAdt78+KWOag@mail.gmail.com","threadId":"28338","inReplyTo":"7v4o0jq2fx.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v3 0/4] Signed push","fromName":"Sverre Rabbelier","fromEmail":"srabbelier@gmail.com","sentAt":"2011-09-11T08:53:24Z","receivedAt":"2011-09-11T08:53:24Z","isPatch":true,"sender":{"key":"srabbelier@gmail.com","avatar":"https://avatars.githubusercontent.com/u/3098?v=4"},"body":"Heya,\n\nOn Sun, Sep 11, 2011 at 03:42, Junio C Hamano <gitster@pobox.com> wrote:\n> I do not know if you read the message Sverre was responding to, but the\n> \"you have to have local copy\" requirement has another and potentially\n> bigger downside (which Sverre did not quote) for people who push out to\n> multiple places.\n\nThat's not what I meant though. I was responding to your \"than you can\nlater _inspect_ the certificates from multiple locations\". I was\nindicating that it would be easier to do such inspection if you can\noptionally fetch the notes from different remotes to different\nlocations in the refs/remotes namespace.\n\n-- \nCheers,\n\nSverre Rabbelier\n"},{"id":"175296","messageId":"20110911155146.GA15820@thunk.org","threadId":"28338","inReplyTo":"7v4o0jq2fx.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v3 0/4] Signed push","fromName":"Ted Ts'o","fromEmail":"tytso@mit.edu","sentAt":"2011-09-11T15:51:46Z","receivedAt":"2011-09-11T15:51:46Z","isPatch":true,"sender":{"key":"tytso@mit.edu","avatar":"https://avatars.githubusercontent.com/u/51416?v=4"},"body":"On Sat, Sep 10, 2011 at 06:42:58PM -0700, Junio C Hamano wrote:\n> Perhaps we shouldn't worry about tag namespace contamination to make\n> things easier and simpler and stop using notes tree?\n\nWith the appropriate conventions, such as using a tag name such as\nsigned-<email>-<timestamp>\" we can at least avoid name conflicts, at\nleast for all practical purposes.\n\nThere is the additional problem that \"git tag -l\" gets painful.  At\nleast for me, though, it's already mostly useless:\n\n   % git tag -l | grep ^v[23] | wc -l\n   858\n\nI can work around this with git aliases that filter out certain\nprefixes that I normally don't care about, but maybe that's something\nthat should be directly supported in git-tag with some git-config\nparameters.\n\nThe final issue that I'd worry about with using tags is performance.\nIf we have hundreds of thousands or millions of tags of the form\nsigned-<email>-<timestamp>, is this going to be a problem?  This does\nseem like something that could be worked around --- in the worst case\nthere could just be a locally maintained reverse index from git commit\nid's to tag names.  (Although as I recall Linus objected to having\nsomething like this for time skews, so maybe he'd object to this too.)\n\n\t       \t    \t     \t       \t     \t  - Ted\n"}]}