All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] commit: teach --gpg-sign option
@ 2011-10-06  0:56 Junio C Hamano
  2011-10-06 15:50 ` Shawn Pearce
                   ` (4 more replies)
  0 siblings, 5 replies; 35+ messages in thread
From: Junio C Hamano @ 2011-10-06  0:56 UTC (permalink / raw)
  To: git

And this uses the gpg-interface.[ch] to allow signing the commit, i.e.

    $ git commit --gpg-sign -m foo
    You need a passphrase to unlock the secret key for
    user: "Junio C Hamano <gitster@pobox.com>"
    4096-bit RSA key, ID 96AFE6CB, created 2011-10-03 (main key ID 713660A7)

    [master 8457d13] foo
     1 files changed, 1 insertions(+), 0 deletions(-)

The lines of GPG detached signature are placed in new header lines, after
the standard tree/parent/author/committer headers, instead of tucking the
signature block at the end of the commit log message text (similar to how
signed tag is done), for multiple reasons:

 - The signature won't clutter output from "git log" and friends if it is
   in the extra header. If we place it at the end of the log message, we
   would need to teach "git log" and friends to strip the signature block
   with an option.

 - Teaching new versions of "git log" and "gitk" to optionally verify and
   show signatures is cleaner if we structurally know where the signature
   block is (instead of scanning in the commit log message).

 - The signature needs to be stripped upon various commit rewriting
   operations, e.g. rebase, filter-branch, etc. They all already ignore
   unknown headers, but if we place signature in the log message, all of
   these tools (and third-party tools) also need to learn how a signature
   block would look like.

 - When we added the optional encoding header, all the tools (both in tree
   and third-party) that acts on the raw commit object should have been
   fixed to ignore headers they do not understand, so it is not like that
   new header would be more likely to break than extra text in the commit.

A commit made with the above sample sequence would look like this:

    $ git cat-file commit HEAD
    tree 3cd71d90e3db4136e5260ab54599791c4f883b9d
    parent b87755351a47b09cb27d6913e6e0e17e6254a4d4
    author Junio C Hamano <gitster@pobox.com> 1317862251 -0700
    committer Junio C Hamano <gitster@pobox.com> 1317862251 -0700
    sig -----BEGIN PGP SIGNATURE-----
    sig Version: GnuPG v1.4.10 (GNU/Linux)
    sig
    sig iQIcBAABAgAGBQJOjPtrAAoJELC16IaWr+bL4TMP/RSe2Y/jYnCkds9unO5JEnfG
    sig ...
    sig =dt98
    sig -----END PGP SIGNATURE-----

    foo

but "git log" (unless you ask for it with --pretty=raw) output is not
cluttered with the signature information.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---

Cf.
    Message-ID: <7vfwjgui8s.fsf_-_@alter.siamese.dyndns.org>
    http://thread.gmane.org/gmane.comp.version-control.git/182297/focus=182384

 builtin/commit-tree.c |   24 +++++++++++++++++++++---
 builtin/commit.c      |   12 ++++++++++--
 builtin/merge.c       |   16 ++++++++++++++--
 commit.c              |   41 ++++++++++++++++++++++++++++++++++++++++-
 commit.h              |    2 +-
 notes-cache.c         |    2 +-
 notes-merge.c         |    2 +-
 7 files changed, 88 insertions(+), 11 deletions(-)

diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c
index d083795..a17811f 100644
--- a/builtin/commit-tree.c
+++ b/builtin/commit-tree.c
@@ -8,8 +8,9 @@
 #include "tree.h"
 #include "builtin.h"
 #include "utf8.h"
+#include "gpg-interface.h"
 
-static const char commit_tree_usage[] = "git commit-tree <sha1> [(-p <sha1>)...] < changelog";
+static const char commit_tree_usage[] = "git commit-tree [-S<signer>] <sha1> [(-p <sha1>)...] < changelog";
 
 static void new_parent(struct commit *parent, struct commit_list **parents_p)
 {
@@ -25,6 +26,14 @@ static void new_parent(struct commit *parent, struct commit_list **parents_p)
 	commit_list_insert(parent, parents_p);
 }
 
+static int commit_tree_config(const char *var, const char *value, void *cb)
+{
+	int status = git_gpg_config(var, value, NULL);
+	if (status)
+		return status;
+	return git_default_config(var, value, cb);
+}
+
 int cmd_commit_tree(int argc, const char **argv, const char *prefix)
 {
 	int i;
@@ -32,11 +41,19 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
 	unsigned char tree_sha1[20];
 	unsigned char commit_sha1[20];
 	struct strbuf buffer = STRBUF_INIT;
+	const char *sign_commit = NULL;
 
-	git_config(git_default_config, NULL);
+	git_config(commit_tree_config, NULL);
 
 	if (argc < 2 || !strcmp(argv[1], "-h"))
 		usage(commit_tree_usage);
+
+	if (!memcmp(argv[1], "-S", 2)) {
+		sign_commit = argv[1] + 2;
+		argv++;
+		argc--;
+	}
+
 	if (get_sha1(argv[1], tree_sha1))
 		die("Not a valid object name %s", argv[1]);
 
@@ -56,7 +73,8 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
 	if (strbuf_read(&buffer, 0, 0) < 0)
 		die_errno("git commit-tree: failed to read");
 
-	if (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) {
+	if (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1,
+			NULL, sign_commit)) {
 		strbuf_release(&buffer);
 		return 1;
 	}
diff --git a/builtin/commit.c b/builtin/commit.c
index cbc9613..90cf7e8 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -26,6 +26,7 @@
 #include "unpack-trees.h"
 #include "quote.h"
 #include "submodule.h"
+#include "gpg-interface.h"
 
 static const char * const builtin_commit_usage[] = {
 	"git commit [options] [--] <filepattern>...",
@@ -85,6 +86,8 @@ static int all, edit_flag, also, interactive, patch_interactive, only, amend, si
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 static int no_post_rewrite, allow_empty_message;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
+static char *sign_commit;
+
 /*
  * The default commit message cleanup mode will remove the lines
  * beginning with # (shell comments) and leading and trailing
@@ -144,6 +147,8 @@ static struct option builtin_commit_options[] = {
 	OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
 	OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
 	OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"),
+	{ OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id",
+	  "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
 	/* end commit message options */
 
 	OPT_GROUP("Commit contents options"),
@@ -1323,6 +1328,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1,
 static int git_commit_config(const char *k, const char *v, void *cb)
 {
 	struct wt_status *s = cb;
+	int status;
 
 	if (!strcmp(k, "commit.template"))
 		return git_config_pathname(&template_file, k, v);
@@ -1330,7 +1336,9 @@ static int git_commit_config(const char *k, const char *v, void *cb)
 		include_status = git_config_bool(k, v);
 		return 0;
 	}
-
+	status = git_gpg_config(k, v, NULL);
+	if (status)
+		return status;
 	return git_status_config(k, v, s);
 }
 
@@ -1481,7 +1489,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 	}
 
 	if (commit_tree(sb.buf, active_cache_tree->sha1, parents, sha1,
-			author_ident.buf)) {
+			author_ident.buf, sign_commit)) {
 		rollback_index_files();
 		die(_("failed to write commit object"));
 	}
diff --git a/builtin/merge.c b/builtin/merge.c
index ab4077f..53cff02 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -26,6 +26,7 @@
 #include "merge-recursive.h"
 #include "resolve-undo.h"
 #include "remote.h"
+#include "gpg-interface.h"
 
 #define DEFAULT_TWOHEAD (1<<0)
 #define DEFAULT_OCTOPUS (1<<1)
@@ -63,6 +64,7 @@ static int allow_rerere_auto;
 static int abort_current_merge;
 static int show_progress = -1;
 static int default_to_upstream;
+static const char *sign_commit;
 
 static struct strategy all_strategy[] = {
 	{ "recursive",  DEFAULT_TWOHEAD | NO_TRIVIAL },
@@ -206,6 +208,8 @@ static struct option builtin_merge_options[] = {
 	OPT_BOOLEAN(0, "abort", &abort_current_merge,
 		"abort the current in-progress merge"),
 	OPT_SET_INT(0, "progress", &show_progress, "force progress reporting", 1),
+	{ OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id",
+	  "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
 	OPT_END()
 };
 
@@ -525,6 +529,8 @@ static void parse_branch_merge_options(char *bmo)
 
 static int git_merge_config(const char *k, const char *v, void *cb)
 {
+	int status;
+
 	if (branch && !prefixcmp(k, "branch.") &&
 		!prefixcmp(k + 7, branch) &&
 		!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
@@ -562,6 +568,10 @@ static int git_merge_config(const char *k, const char *v, void *cb)
 		default_to_upstream = git_config_bool(k, v);
 		return 0;
 	}
+
+	status = git_gpg_config(k, v, NULL);
+	if (status)
+		return status;
 	return git_diff_ui_config(k, v, cb);
 }
 
@@ -870,7 +880,8 @@ static int merge_trivial(void)
 	parent->next->item = remoteheads->item;
 	parent->next->next = NULL;
 	run_prepare_commit_msg();
-	commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
+	commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL,
+		    sign_commit);
 	finish(result_commit, "In-index merge");
 	drop_save();
 	return 0;
@@ -900,7 +911,8 @@ static int finish_automerge(struct commit_list *common,
 	free_commit_list(remoteheads);
 	strbuf_addch(&merge_msg, '\n');
 	run_prepare_commit_msg();
-	commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
+	commit_tree(merge_msg.buf, result_tree, parents, result_commit,
+		    NULL, sign_commit);
 	strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
 	finish(result_commit, buf.buf);
 	strbuf_release(&buf);
diff --git a/commit.c b/commit.c
index 97b4327..969435d 100644
--- a/commit.c
+++ b/commit.c
@@ -6,6 +6,7 @@
 #include "diff.h"
 #include "revision.h"
 #include "notes.h"
+#include "gpg-interface.h"
 
 int save_commit_buffer = 1;
 
@@ -814,6 +815,41 @@ struct commit_list *reduce_heads(struct commit_list *heads)
 	return result;
 }
 
+static int do_sign_commit(struct strbuf *buf, const char *keyid)
+{
+	struct strbuf sig = STRBUF_INIT;
+	int inspos, copypos;
+	const char gpg_sig[] = "sig ";
+	const int header_len = sizeof(gpg_sig) - 1;
+
+	/* find the end of the header */
+	inspos = strstr(buf->buf, "\n\n") - buf->buf + 1;
+	copypos = buf->len;
+
+	strbuf_addbuf(&sig, buf);
+
+	if (!keyid || !*keyid)
+		keyid = get_signing_key();
+	if (sign_buffer(&sig, keyid)) {
+		strbuf_release(&sig);
+		return -1;
+	}
+
+	while (sig.buf[copypos]) {
+		const char *bol = sig.buf + copypos;
+		const char *eol = strchrnul(bol, '\n');
+		int len = (eol - bol) + !!*eol;
+		strbuf_insert(buf, inspos, gpg_sig, header_len);
+		inspos += header_len;
+		strbuf_insert(buf, inspos, bol, len);
+		inspos += len;
+		copypos += len;
+	}
+	strbuf_release(&sig);
+	return 0;
+}
+
+
 static const char commit_utf8_warn[] =
 "Warning: commit message does not conform to UTF-8.\n"
 "You may want to amend it after fixing the message, or set the config\n"
@@ -821,7 +857,7 @@ static const char commit_utf8_warn[] =
 
 int commit_tree(const char *msg, unsigned char *tree,
 		struct commit_list *parents, unsigned char *ret,
-		const char *author)
+		const char *author, const char *sign_commit)
 {
 	int result;
 	int encoding_is_utf8;
@@ -864,6 +900,9 @@ int commit_tree(const char *msg, unsigned char *tree,
 	if (encoding_is_utf8 && !is_utf8(buffer.buf))
 		fprintf(stderr, commit_utf8_warn);
 
+	if (sign_commit && do_sign_commit(&buffer, sign_commit))
+		return -1;
+
 	result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
 	strbuf_release(&buffer);
 	return result;
diff --git a/commit.h b/commit.h
index 12d100b8..8c2419b 100644
--- a/commit.h
+++ b/commit.h
@@ -175,6 +175,6 @@ struct commit_list *reduce_heads(struct commit_list *heads);
 
 extern int commit_tree(const char *msg, unsigned char *tree,
 		struct commit_list *parents, unsigned char *ret,
-		const char *author);
+		       const char *author, const char *sign_commit);
 
 #endif /* COMMIT_H */
diff --git a/notes-cache.c b/notes-cache.c
index 4c8984e..c36a960 100644
--- a/notes-cache.c
+++ b/notes-cache.c
@@ -56,7 +56,7 @@ int notes_cache_write(struct notes_cache *c)
 
 	if (write_notes_tree(&c->tree, tree_sha1))
 		return -1;
-	if (commit_tree(c->validity, tree_sha1, NULL, commit_sha1, NULL) < 0)
+	if (commit_tree(c->validity, tree_sha1, NULL, commit_sha1, NULL, NULL) < 0)
 		return -1;
 	if (update_ref("update notes cache", c->tree.ref, commit_sha1, NULL,
 		       0, QUIET_ON_ERR) < 0)
diff --git a/notes-merge.c b/notes-merge.c
index e1aaf43..c29c434 100644
--- a/notes-merge.c
+++ b/notes-merge.c
@@ -546,7 +546,7 @@ void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
 		/* else: t->ref points to nothing, assume root/orphan commit */
 	}
 
-	if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL))
+	if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL, NULL))
 		die("Failed to commit notes tree to database");
 }
 
-- 
1.7.7.138.g7f41b6

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* Re: [PATCH] commit: teach --gpg-sign option
  2011-10-06  0:56 [PATCH] commit: teach --gpg-sign option Junio C Hamano
@ 2011-10-06 15:50 ` Shawn Pearce
  2011-10-06 17:11   ` Jonathan Nieder
  2011-10-06 22:24 ` Robin H. Johnson
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 35+ messages in thread
From: Shawn Pearce @ 2011-10-06 15:50 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On Wed, Oct 5, 2011 at 17:56, Junio C Hamano <gitster@pobox.com> wrote:
> And this uses the gpg-interface.[ch] to allow signing the commit, i.e.
>
>    $ git commit --gpg-sign -m foo
>    You need a passphrase to unlock the secret key for
>    user: "Junio C Hamano <gitster@pobox.com>"
>    4096-bit RSA key, ID 96AFE6CB, created 2011-10-03 (main key ID 713660A7)
>
>    [master 8457d13] foo
>     1 files changed, 1 insertions(+), 0 deletions(-)
>
> The lines of GPG detached signature are placed in new header lines, after
> the standard tree/parent/author/committer headers, instead of tucking the
> signature block at the end of the commit log message text (similar to how
> signed tag is done), for multiple reasons:

I like this approach better than the prior "push certificate" idea.
The signature information is part of the history graph, and won't be
stripped or forgotten to be published as commits flow through
development trees, provided that the commit SHA-1 was preserved by not
doing a rewrite. There is no merge race on the server side to get a
branch updated.

The signature is automatically stripped by older Git tools like commit
--amend, format-patch or rebase, where the signature would be made
invalid anyway by changing the tree, parent or committer. That is a
nice bit of backward compatibility there. Of course there is some
concern this signature data will show up incorrectly on clients that
are so ancient they don't understand the "encoding" header, but those
clients are already very ancient, and already do not process encoding
correctly.

-- 
Shawn.

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] commit: teach --gpg-sign option
  2011-10-06 15:50 ` Shawn Pearce
@ 2011-10-06 17:11   ` Jonathan Nieder
  2011-10-06 17:22     ` Matthieu Moy
  2011-10-06 21:29     ` Junio C Hamano
  0 siblings, 2 replies; 35+ messages in thread
From: Jonathan Nieder @ 2011-10-06 17:11 UTC (permalink / raw)
  To: Shawn Pearce; +Cc: Junio C Hamano, git

Shawn Pearce wrote:
> On Wed, Oct 5, 2011 at 17:56, Junio C Hamano <gitster@pobox.com> wrote:

>> And this uses the gpg-interface.[ch] to allow signing the commit, i.e.
>>
>>    $ git commit --gpg-sign -m foo
>>    You need a passphrase to unlock the secret key for
>>    user: "Junio C Hamano <gitster@pobox.com>"
>>    4096-bit RSA key, ID 96AFE6CB, created 2011-10-03 (main key ID 713660A7)
[...]
> I like this approach better than the prior "push certificate" idea.
> The signature information is part of the history graph

I probably missed some earlier discussion (so please forgive me this),
but how is it intended to be used?  Would projects

 a. require as a matter of policy that all commits be signed
 b. just sign releases as usual, but as commits in the history graph
    instead of tags
 c. sign the occasional especially interesting commit

What happens if my old key is compromised and I want to throw away the
signatures and replace them with signatures using my new key?  How
does this relate to the "push certificate" use case, which seemed to
be mostly about authenticating published branch tips with signatures
that are not necessarily important in the long term?

In other words, something like this feature sounds like a sensible way
to commit the equivalent of a GPG-signed patch, but it doesn't seem
like a good fit for the "push certificate" use cases.

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] commit: teach --gpg-sign option
  2011-10-06 17:11   ` Jonathan Nieder
@ 2011-10-06 17:22     ` Matthieu Moy
  2011-10-06 18:44       ` Michael J Gruber
  2011-10-06 21:29     ` Junio C Hamano
  1 sibling, 1 reply; 35+ messages in thread
From: Matthieu Moy @ 2011-10-06 17:22 UTC (permalink / raw)
  To: Jonathan Nieder; +Cc: Shawn Pearce, Junio C Hamano, git

Jonathan Nieder <jrnieder@gmail.com> writes:

> I probably missed some earlier discussion (so please forgive me this),

(same here)

> What happens if my old key is compromised and I want to throw away the
> signatures and replace them with signatures using my new key?

With the patch we're discussing, signatures are part of history, hence
can't be modified after the fact without rewritting them.

*But*, by design, unless sha1 itself is compromized (in which case Git
would need to change to another hash function, that would be no fun),
signing the tip of every branch is sufficient to sign the whole history.

So, your old signatures would remain there, and your new signature, for
new commits, would be added on top.

> How does this relate to the "push certificate" use case, which seemed
> to be mostly about authenticating published branch tips with
> signatures that are not necessarily important in the long term?

I'm wondering how this feature would fit in a typical flow, indeed.
Usually, I hack for a while, and when I'm happy enough, I push. But I
don't take the decision of what to push at commit time, so if the idea
is to sign only a few commits (i.e. the ones you push), then you should
decide this at commit time ("hmm, I should commit --gpg-sign this time
because I'm going to push this one").

If the idea is to sign every commit, then there should be a config
option so that we don't have to type it every time.

-- 
Matthieu Moy
http://www-verimag.imag.fr/~moy/

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] commit: teach --gpg-sign option
  2011-10-06 17:22     ` Matthieu Moy
@ 2011-10-06 18:44       ` Michael J Gruber
  0 siblings, 0 replies; 35+ messages in thread
From: Michael J Gruber @ 2011-10-06 18:44 UTC (permalink / raw)
  To: Matthieu Moy; +Cc: Jonathan Nieder, Shawn Pearce, Junio C Hamano, git

Matthieu Moy venit, vidit, dixit 06.10.2011 19:22:
> Jonathan Nieder <jrnieder@gmail.com> writes:
> 
>> I probably missed some earlier discussion (so please forgive me this),
> 
> (same here)
> 
>> What happens if my old key is compromised and I want to throw away the
>> signatures and replace them with signatures using my new key?
> 
> With the patch we're discussing, signatures are part of history, hence
> can't be modified after the fact without rewritting them.
> 
> *But*, by design, unless sha1 itself is compromized (in which case Git
> would need to change to another hash function, that would be no fun),
> signing the tip of every branch is sufficient to sign the whole history.
> 
> So, your old signatures would remain there, and your new signature, for
> new commits, would be added on top.
> 
>> How does this relate to the "push certificate" use case, which seemed
>> to be mostly about authenticating published branch tips with
>> signatures that are not necessarily important in the long term?
> 
> I'm wondering how this feature would fit in a typical flow, indeed.
> Usually, I hack for a while, and when I'm happy enough, I push. But I
> don't take the decision of what to push at commit time, so if the idea
> is to sign only a few commits (i.e. the ones you push), then you should
> decide this at commit time ("hmm, I should commit --gpg-sign this time
> because I'm going to push this one").
> 
> If the idea is to sign every commit, then there should be a config
> option so that we don't have to type it every time.
> 

Same concerns here. You can always

git commit --amend --gpg-sign

the commit at the tip, of course, and can even set things up to have
push do this or remind you thereof.

I really liked the signatures as notes (because I feel those signatures
are attachments after the fact, not part of the commit) but can see how
distributing and merging them is non-trivial, and similarly checking
them at a point in time when they are not in a notes tree yet.

Michael

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] commit: teach --gpg-sign option
  2011-10-06 17:11   ` Jonathan Nieder
  2011-10-06 17:22     ` Matthieu Moy
@ 2011-10-06 21:29     ` Junio C Hamano
  1 sibling, 0 replies; 35+ messages in thread
From: Junio C Hamano @ 2011-10-06 21:29 UTC (permalink / raw)
  To: Jonathan Nieder; +Cc: Shawn Pearce, git

Jonathan Nieder <jrnieder@gmail.com> writes:

>> I like this approach better than the prior "push certificate" idea.
>> The signature information is part of the history graph
>
> I probably missed some earlier discussion (so please forgive me this),

Heh, you are not forgiven when the original message has a clear pointer to
the previous discussion ;-).

> but how is it intended to be used?  Would projects
>
>  a. require as a matter of policy that all commits be signed

Possible. Personally I would _not_ advise it but they can send in a patch
to add a configuration or two if they do want to run their project that
way.

>  b. just sign releases as usual, but as commits in the history graph
>     instead of tags

This is not meant to replace tags that is attached after the fact. If
anything...

>  c. sign the occasional especially interesting commit

...with the definition of "interesting" being "this is tonight's tip of
branch Linus is pushing out between releases", "I shortly will ask Linus
to pull from the history leading up to this commit", etc., this is the
primary scenario I personally envision the feature would be used in.
Without having to have "nightly" signed tags that clutter the tag
namespace, we can gain more confidence in the integrity of the history
between officially tagged release points that may be a few months apart,
depending on projects.

> ... How
> does this relate to the "push certificate" use case, which seemed to
> be mostly about authenticating published branch tips with signatures
> that are not necessarily important in the long term?

To the upstream project whose participants are signing its history, these
publish points may not be important in the longer term, but for downstream
consumers that have to fork from an in-between point for the next embedded
device release track, it serves the same purpose as push certificates and
is equally important: it allows them to limit the length of near-tip
history that might have been tampered that needs to be validated. If the
downstream consumers fork only from a signed commit point, they only need
to audit their own history without worrying about imported stuff after
incident like what k.org had recently.

I am also somewhat disturbed by "have to sign when committing, long before
I am confident that this is worth pushing" aspect of this approach, but I
do not think it would be much of an issue in practice.

 - If you are only pubishing one independent branch, it is just the matter
   of either "commit --amend --gpg-sign" or "commit --allow-empty --gpg-sign"
   before you push;

 - If you are publishing multiple related branches (e.g. maint, master,
   next) like I do, and want to correct a mistake discovered at a lower
   branch (e.g. master) after it has been already merged in higher
   branches (e.g. next), you have to either amend the tip of the lower
   branch and rebuild the higher branches, or queue a fix-up to the tip of
   the lower branch and merge the result to the higher branches _anyway_,
   before you push.

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] commit: teach --gpg-sign option
  2011-10-06  0:56 [PATCH] commit: teach --gpg-sign option Junio C Hamano
  2011-10-06 15:50 ` Shawn Pearce
@ 2011-10-06 22:24 ` Robin H. Johnson
  2011-10-07  8:40   ` Michael J Gruber
  2011-10-09 20:00 ` Michael J Gruber
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 35+ messages in thread
From: Robin H. Johnson @ 2011-10-06 22:24 UTC (permalink / raw)
  To: Git Mailing List

On Wed, Oct 05, 2011 at 05:56:55PM -0700,  Junio C Hamano wrote:
> And this uses the gpg-interface.[ch] to allow signing the commit, i.e.
> 
>     $ git commit --gpg-sign -m foo
>     You need a passphrase to unlock the secret key for
>     user: "Junio C Hamano <gitster@pobox.com>"
>     4096-bit RSA key, ID 96AFE6CB, created 2011-10-03 (main key ID 713660A7)
> 
>     [master 8457d13] foo
>      1 files changed, 1 insertions(+), 0 deletions(-)
I like it, but I have a couple of questions: 
1. Are the sig lines used in computed SHA1/commitid of a given commit (I
   see examples w/ --amend and that would usually change the SHA1)?
2. Can we allow more than one person sign a commit?
3. If I have prepared a series on a local branch, and I want to sign all
   of them, is this a variant of rebase or?

I think this isn't a replacement for push certificates, but has value in
itself. It's certainly provides better integration than the
signature-in-note variants.

-- 
Robin Hugh Johnson
Gentoo Linux: Developer, Trustee & Infrastructure Lead
E-Mail     : robbat2@gentoo.org
GnuPG FP   : 11AC BA4F 4778 E3F6 E4ED  F38E B27B 944E 3488 4E85

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] commit: teach --gpg-sign option
  2011-10-06 22:24 ` Robin H. Johnson
@ 2011-10-07  8:40   ` Michael J Gruber
  2011-10-07 11:18     ` Nguyen Thai Ngoc Duy
                       ` (2 more replies)
  0 siblings, 3 replies; 35+ messages in thread
From: Michael J Gruber @ 2011-10-07  8:40 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Robin H. Johnson, Junio C Hamano

[readding JCH to cc whom you dropped]
Robin H. Johnson venit, vidit, dixit 07.10.2011 00:24:
> On Wed, Oct 05, 2011 at 05:56:55PM -0700,  Junio C Hamano wrote:
>> And this uses the gpg-interface.[ch] to allow signing the commit, i.e.
>>
>>     $ git commit --gpg-sign -m foo
>>     You need a passphrase to unlock the secret key for
>>     user: "Junio C Hamano <gitster@pobox.com>"
>>     4096-bit RSA key, ID 96AFE6CB, created 2011-10-03 (main key ID 713660A7)
>>
>>     [master 8457d13] foo
>>      1 files changed, 1 insertions(+), 0 deletions(-)
> I like it, but I have a couple of questions: 
> 1. Are the sig lines used in computed SHA1/commitid of a given commit (I
>    see examples w/ --amend and that would usually change the SHA1)?

Yes, just like with tag objects.

> 2. Can we allow more than one person sign a commit?

I don't think we support it now (tags) but we could allow concatenating
signatures since they are detached.

There's a somewhat delicate issue here: The signature (tag/commit) is a
signature on the contents of the object, and is itself not part of the
contents (or else we would have a chicken-egg-problem).

The sha1 of the object is determined by the content+header, i.e.
including the signature.

So, by adding a signature, you change the sha1, but any existing
signature remains valid.

This is also how you can try to achieve a specific sha1 for a given
object content...

> 3. If I have prepared a series on a local branch, and I want to sign all
>    of them, is this a variant of rebase or?

If you really want to sign all you can rebase-i and use "exec" to do
that automatically, but there's no point: signing the top-most commit
serves the same purpose.

> I think this isn't a replacement for push certificates, but has value in
> itself. It's certainly provides better integration than the
> signature-in-note variants.
> 

I do think it's meant as an implementation of push certificates. I don't
see any other value in it which could not be achieved by signed tags.
Can you describe any?

Michael

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] commit: teach --gpg-sign option
  2011-10-07  8:40   ` Michael J Gruber
@ 2011-10-07 11:18     ` Nguyen Thai Ngoc Duy
  2011-10-09 16:32     ` Michael J Gruber
  2011-10-09 22:57     ` Robin H. Johnson
  2 siblings, 0 replies; 35+ messages in thread
From: Nguyen Thai Ngoc Duy @ 2011-10-07 11:18 UTC (permalink / raw)
  To: Michael J Gruber; +Cc: Git Mailing List, Robin H. Johnson, Junio C Hamano

On Fri, Oct 7, 2011 at 7:40 PM, Michael J Gruber
<git@drmicha.warpmail.net> wrote:
>> 3. If I have prepared a series on a local branch, and I want to sign all
>>    of them, is this a variant of rebase or?
>
> If you really want to sign all you can rebase-i and use "exec" to do
> that automatically, but there's no point: signing the top-most commit
> serves the same purpose.

I think Gentoo wants identity, who (typically a Gentoo dev) signs this
particular commit and takes responsibility for making sure the commit
follows all kinds of Gentoo requirements. Commits can be passed
around, author and committer are not a reliable source.
-- 
Duy

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] commit: teach --gpg-sign option
  2011-10-07  8:40   ` Michael J Gruber
  2011-10-07 11:18     ` Nguyen Thai Ngoc Duy
@ 2011-10-09 16:32     ` Michael J Gruber
  2011-10-09 22:57     ` Robin H. Johnson
  2 siblings, 0 replies; 35+ messages in thread
From: Michael J Gruber @ 2011-10-09 16:32 UTC (permalink / raw)
  Cc: Git Mailing List, Robin H. Johnson, Junio C Hamano

Michael J Gruber venit, vidit, dixit 07.10.2011 10:40:
> [readding JCH to cc whom you dropped]
> Robin H. Johnson venit, vidit, dixit 07.10.2011 00:24:
>> On Wed, Oct 05, 2011 at 05:56:55PM -0700,  Junio C Hamano wrote:
>>> And this uses the gpg-interface.[ch] to allow signing the commit, i.e.
>>>
>>>     $ git commit --gpg-sign -m foo
>>>     You need a passphrase to unlock the secret key for
>>>     user: "Junio C Hamano <gitster@pobox.com>"
>>>     4096-bit RSA key, ID 96AFE6CB, created 2011-10-03 (main key ID 713660A7)
>>>
>>>     [master 8457d13] foo
>>>      1 files changed, 1 insertions(+), 0 deletions(-)
>> I like it, but I have a couple of questions: 
>> 1. Are the sig lines used in computed SHA1/commitid of a given commit (I
>>    see examples w/ --amend and that would usually change the SHA1)?
> 
> Yes, just like with tag objects.
> 
>> 2. Can we allow more than one person sign a commit?
> 
> I don't think we support it now (tags) but we could allow concatenating
> signatures since they are detached.

Quick update:
Sticking two signatures into a signed tag works perfectly with current
git, both signatures are verified and displayed.

So, it might make sense to have "commit --amend" append to an existing
signature.

> There's a somewhat delicate issue here: The signature (tag/commit) is a
> signature on the contents of the object, and is itself not part of the
> contents (or else we would have a chicken-egg-problem).
> 
> The sha1 of the object is determined by the content+header, i.e.
> including the signature.

NB: "header" is the wrong term here, it's "data" I think.

> So, by adding a signature, you change the sha1, but any existing
> signature remains valid.
> 
> This is also how you can try to achieve a specific sha1 for a given
> object content...
> 

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] commit: teach --gpg-sign option
  2011-10-06  0:56 [PATCH] commit: teach --gpg-sign option Junio C Hamano
  2011-10-06 15:50 ` Shawn Pearce
  2011-10-06 22:24 ` Robin H. Johnson
@ 2011-10-09 20:00 ` Michael J Gruber
  2011-10-09 21:22   ` Junio C Hamano
  2011-10-09 22:27   ` Junio C Hamano
       [not found] ` <CACBZZX6xsnAv4S8zAqi08bcqrghZ8nKdzFP=UNCqZOqrEeLFnA@mail.gmail.com>
  2011-10-19  0:20 ` [PATCH v3 0/3] Signed-commit Junio C Hamano
  4 siblings, 2 replies; 35+ messages in thread
From: Michael J Gruber @ 2011-10-09 20:00 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

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

I just noticed that this format differs from the one of signed tags.
What special reason is there for the "sig " indentation?

BTW: commit --amend --gpg-sign strips an existing signature rather than
adding one. We might want the user to have a say here.

> but "git log" (unless you ask for it with --pretty=raw) output is not
> cluttered with the signature information.
> 
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
> 
> Cf.
>     Message-ID: <7vfwjgui8s.fsf_-_@alter.siamese.dyndns.org>
>     http://thread.gmane.org/gmane.comp.version-control.git/182297/focus=182384
> 
>  builtin/commit-tree.c |   24 +++++++++++++++++++++---
>  builtin/commit.c      |   12 ++++++++++--
>  builtin/merge.c       |   16 ++++++++++++++--
>  commit.c              |   41 ++++++++++++++++++++++++++++++++++++++++-
>  commit.h              |    2 +-
>  notes-cache.c         |    2 +-
>  notes-merge.c         |    2 +-
>  7 files changed, 88 insertions(+), 11 deletions(-)
> 
> diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c
> index d083795..a17811f 100644
> --- a/builtin/commit-tree.c
> +++ b/builtin/commit-tree.c
> @@ -8,8 +8,9 @@
>  #include "tree.h"
>  #include "builtin.h"
>  #include "utf8.h"
> +#include "gpg-interface.h"
>  
> -static const char commit_tree_usage[] = "git commit-tree <sha1> [(-p <sha1>)...] < changelog";
> +static const char commit_tree_usage[] = "git commit-tree [-S<signer>] <sha1> [(-p <sha1>)...] < changelog";
>  
>  static void new_parent(struct commit *parent, struct commit_list **parents_p)
>  {
> @@ -25,6 +26,14 @@ static void new_parent(struct commit *parent, struct commit_list **parents_p)
>  	commit_list_insert(parent, parents_p);
>  }
>  
> +static int commit_tree_config(const char *var, const char *value, void *cb)
> +{
> +	int status = git_gpg_config(var, value, NULL);
> +	if (status)
> +		return status;
> +	return git_default_config(var, value, cb);
> +}
> +
>  int cmd_commit_tree(int argc, const char **argv, const char *prefix)
>  {
>  	int i;
> @@ -32,11 +41,19 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
>  	unsigned char tree_sha1[20];
>  	unsigned char commit_sha1[20];
>  	struct strbuf buffer = STRBUF_INIT;
> +	const char *sign_commit = NULL;
>  
> -	git_config(git_default_config, NULL);
> +	git_config(commit_tree_config, NULL);
>  
>  	if (argc < 2 || !strcmp(argv[1], "-h"))
>  		usage(commit_tree_usage);
> +
> +	if (!memcmp(argv[1], "-S", 2)) {
> +		sign_commit = argv[1] + 2;
> +		argv++;
> +		argc--;
> +	}
> +
>  	if (get_sha1(argv[1], tree_sha1))
>  		die("Not a valid object name %s", argv[1]);
>  
> @@ -56,7 +73,8 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
>  	if (strbuf_read(&buffer, 0, 0) < 0)
>  		die_errno("git commit-tree: failed to read");
>  
> -	if (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) {
> +	if (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1,
> +			NULL, sign_commit)) {
>  		strbuf_release(&buffer);
>  		return 1;
>  	}
> diff --git a/builtin/commit.c b/builtin/commit.c
> index cbc9613..90cf7e8 100644
> --- a/builtin/commit.c
> +++ b/builtin/commit.c
> @@ -26,6 +26,7 @@
>  #include "unpack-trees.h"
>  #include "quote.h"
>  #include "submodule.h"
> +#include "gpg-interface.h"
>  
>  static const char * const builtin_commit_usage[] = {
>  	"git commit [options] [--] <filepattern>...",
> @@ -85,6 +86,8 @@ static int all, edit_flag, also, interactive, patch_interactive, only, amend, si
>  static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
>  static int no_post_rewrite, allow_empty_message;
>  static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
> +static char *sign_commit;
> +
>  /*
>   * The default commit message cleanup mode will remove the lines
>   * beginning with # (shell comments) and leading and trailing
> @@ -144,6 +147,8 @@ static struct option builtin_commit_options[] = {
>  	OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
>  	OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
>  	OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"),
> +	{ OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id",
> +	  "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
>  	/* end commit message options */
>  
>  	OPT_GROUP("Commit contents options"),
> @@ -1323,6 +1328,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1,
>  static int git_commit_config(const char *k, const char *v, void *cb)
>  {
>  	struct wt_status *s = cb;
> +	int status;
>  
>  	if (!strcmp(k, "commit.template"))
>  		return git_config_pathname(&template_file, k, v);
> @@ -1330,7 +1336,9 @@ static int git_commit_config(const char *k, const char *v, void *cb)
>  		include_status = git_config_bool(k, v);
>  		return 0;
>  	}
> -
> +	status = git_gpg_config(k, v, NULL);
> +	if (status)
> +		return status;
>  	return git_status_config(k, v, s);
>  }
>  
> @@ -1481,7 +1489,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
>  	}
>  
>  	if (commit_tree(sb.buf, active_cache_tree->sha1, parents, sha1,
> -			author_ident.buf)) {
> +			author_ident.buf, sign_commit)) {
>  		rollback_index_files();
>  		die(_("failed to write commit object"));
>  	}
> diff --git a/builtin/merge.c b/builtin/merge.c
> index ab4077f..53cff02 100644
> --- a/builtin/merge.c
> +++ b/builtin/merge.c
> @@ -26,6 +26,7 @@
>  #include "merge-recursive.h"
>  #include "resolve-undo.h"
>  #include "remote.h"
> +#include "gpg-interface.h"
>  
>  #define DEFAULT_TWOHEAD (1<<0)
>  #define DEFAULT_OCTOPUS (1<<1)
> @@ -63,6 +64,7 @@ static int allow_rerere_auto;
>  static int abort_current_merge;
>  static int show_progress = -1;
>  static int default_to_upstream;
> +static const char *sign_commit;
>  
>  static struct strategy all_strategy[] = {
>  	{ "recursive",  DEFAULT_TWOHEAD | NO_TRIVIAL },
> @@ -206,6 +208,8 @@ static struct option builtin_merge_options[] = {
>  	OPT_BOOLEAN(0, "abort", &abort_current_merge,
>  		"abort the current in-progress merge"),
>  	OPT_SET_INT(0, "progress", &show_progress, "force progress reporting", 1),
> +	{ OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id",
> +	  "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
>  	OPT_END()
>  };
>  
> @@ -525,6 +529,8 @@ static void parse_branch_merge_options(char *bmo)
>  
>  static int git_merge_config(const char *k, const char *v, void *cb)
>  {
> +	int status;
> +
>  	if (branch && !prefixcmp(k, "branch.") &&
>  		!prefixcmp(k + 7, branch) &&
>  		!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
> @@ -562,6 +568,10 @@ static int git_merge_config(const char *k, const char *v, void *cb)
>  		default_to_upstream = git_config_bool(k, v);
>  		return 0;
>  	}
> +
> +	status = git_gpg_config(k, v, NULL);
> +	if (status)
> +		return status;
>  	return git_diff_ui_config(k, v, cb);
>  }
>  
> @@ -870,7 +880,8 @@ static int merge_trivial(void)
>  	parent->next->item = remoteheads->item;
>  	parent->next->next = NULL;
>  	run_prepare_commit_msg();
> -	commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
> +	commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL,
> +		    sign_commit);
>  	finish(result_commit, "In-index merge");
>  	drop_save();
>  	return 0;
> @@ -900,7 +911,8 @@ static int finish_automerge(struct commit_list *common,
>  	free_commit_list(remoteheads);
>  	strbuf_addch(&merge_msg, '\n');
>  	run_prepare_commit_msg();
> -	commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
> +	commit_tree(merge_msg.buf, result_tree, parents, result_commit,
> +		    NULL, sign_commit);
>  	strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
>  	finish(result_commit, buf.buf);
>  	strbuf_release(&buf);
> diff --git a/commit.c b/commit.c
> index 97b4327..969435d 100644
> --- a/commit.c
> +++ b/commit.c
> @@ -6,6 +6,7 @@
>  #include "diff.h"
>  #include "revision.h"
>  #include "notes.h"
> +#include "gpg-interface.h"
>  
>  int save_commit_buffer = 1;
>  
> @@ -814,6 +815,41 @@ struct commit_list *reduce_heads(struct commit_list *heads)
>  	return result;
>  }
>  
> +static int do_sign_commit(struct strbuf *buf, const char *keyid)
> +{
> +	struct strbuf sig = STRBUF_INIT;
> +	int inspos, copypos;
> +	const char gpg_sig[] = "sig ";
> +	const int header_len = sizeof(gpg_sig) - 1;
> +
> +	/* find the end of the header */
> +	inspos = strstr(buf->buf, "\n\n") - buf->buf + 1;
> +	copypos = buf->len;
> +
> +	strbuf_addbuf(&sig, buf);
> +
> +	if (!keyid || !*keyid)
> +		keyid = get_signing_key();
> +	if (sign_buffer(&sig, keyid)) {
> +		strbuf_release(&sig);
> +		return -1;
> +	}
> +
> +	while (sig.buf[copypos]) {
> +		const char *bol = sig.buf + copypos;
> +		const char *eol = strchrnul(bol, '\n');
> +		int len = (eol - bol) + !!*eol;
> +		strbuf_insert(buf, inspos, gpg_sig, header_len);
> +		inspos += header_len;
> +		strbuf_insert(buf, inspos, bol, len);
> +		inspos += len;
> +		copypos += len;
> +	}
> +	strbuf_release(&sig);
> +	return 0;
> +}
> +
> +
>  static const char commit_utf8_warn[] =
>  "Warning: commit message does not conform to UTF-8.\n"
>  "You may want to amend it after fixing the message, or set the config\n"
> @@ -821,7 +857,7 @@ static const char commit_utf8_warn[] =
>  
>  int commit_tree(const char *msg, unsigned char *tree,
>  		struct commit_list *parents, unsigned char *ret,
> -		const char *author)
> +		const char *author, const char *sign_commit)
>  {
>  	int result;
>  	int encoding_is_utf8;
> @@ -864,6 +900,9 @@ int commit_tree(const char *msg, unsigned char *tree,
>  	if (encoding_is_utf8 && !is_utf8(buffer.buf))
>  		fprintf(stderr, commit_utf8_warn);
>  
> +	if (sign_commit && do_sign_commit(&buffer, sign_commit))
> +		return -1;
> +
>  	result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
>  	strbuf_release(&buffer);
>  	return result;
> diff --git a/commit.h b/commit.h
> index 12d100b8..8c2419b 100644
> --- a/commit.h
> +++ b/commit.h
> @@ -175,6 +175,6 @@ struct commit_list *reduce_heads(struct commit_list *heads);
>  
>  extern int commit_tree(const char *msg, unsigned char *tree,
>  		struct commit_list *parents, unsigned char *ret,
> -		const char *author);
> +		       const char *author, const char *sign_commit);
>  
>  #endif /* COMMIT_H */
> diff --git a/notes-cache.c b/notes-cache.c
> index 4c8984e..c36a960 100644
> --- a/notes-cache.c
> +++ b/notes-cache.c
> @@ -56,7 +56,7 @@ int notes_cache_write(struct notes_cache *c)
>  
>  	if (write_notes_tree(&c->tree, tree_sha1))
>  		return -1;
> -	if (commit_tree(c->validity, tree_sha1, NULL, commit_sha1, NULL) < 0)
> +	if (commit_tree(c->validity, tree_sha1, NULL, commit_sha1, NULL, NULL) < 0)
>  		return -1;
>  	if (update_ref("update notes cache", c->tree.ref, commit_sha1, NULL,
>  		       0, QUIET_ON_ERR) < 0)
> diff --git a/notes-merge.c b/notes-merge.c
> index e1aaf43..c29c434 100644
> --- a/notes-merge.c
> +++ b/notes-merge.c
> @@ -546,7 +546,7 @@ void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
>  		/* else: t->ref points to nothing, assume root/orphan commit */
>  	}
>  
> -	if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL))
> +	if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL, NULL))
>  		die("Failed to commit notes tree to database");
>  }
>  

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] commit: teach --gpg-sign option
  2011-10-09 20:00 ` Michael J Gruber
@ 2011-10-09 21:22   ` Junio C Hamano
  2011-10-10  6:33     ` Michael J Gruber
  2011-10-09 22:27   ` Junio C Hamano
  1 sibling, 1 reply; 35+ messages in thread
From: Junio C Hamano @ 2011-10-09 21:22 UTC (permalink / raw)
  To: Michael J Gruber; +Cc: git

Michael J Gruber <git@drmicha.warpmail.net> writes:

> I just noticed that this format differs from the one of signed tags.
> What special reason is there for the "sig " indentation?

Read the part of the message you are quoting.

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] commit: teach --gpg-sign option
  2011-10-09 20:00 ` Michael J Gruber
  2011-10-09 21:22   ` Junio C Hamano
@ 2011-10-09 22:27   ` Junio C Hamano
  2011-10-10  6:33     ` Michael J Gruber
  1 sibling, 1 reply; 35+ messages in thread
From: Junio C Hamano @ 2011-10-09 22:27 UTC (permalink / raw)
  To: Michael J Gruber; +Cc: git

Michael J Gruber <git@drmicha.warpmail.net> writes:

> BTW: commit --amend --gpg-sign strips an existing signature rather than
> adding one. We might want the user to have a say here.

I think it deserves a separate command (commit --add-gpg-sign) that is
used _only_ to add an additional signature by another person without
affecting anything else in the commit (i.e. the tree, the parents and the
author and committership information) from the viewpoint of the workflow,

Obviously that "add-signature" mode needs to be aware of the existing
signature. It is a deliberate design decision to strip existing signature
when anything in the commit changes, which is the norm for --amend.

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] commit: teach --gpg-sign option
  2011-10-07  8:40   ` Michael J Gruber
  2011-10-07 11:18     ` Nguyen Thai Ngoc Duy
  2011-10-09 16:32     ` Michael J Gruber
@ 2011-10-09 22:57     ` Robin H. Johnson
  2011-10-09 23:18       ` Junio C Hamano
  2 siblings, 1 reply; 35+ messages in thread
From: Robin H. Johnson @ 2011-10-09 22:57 UTC (permalink / raw)
  To: Michael J Gruber, Git Mailing List; +Cc: 	Robin H. Johnson, Junio C Hamano

On Fri, Oct 07, 2011 at 10:40:30AM +0200,  Michael J Gruber wrote:
> [readding JCH to cc whom you dropped]
> Robin H. Johnson venit, vidit, dixit 07.10.2011 00:24:
> > On Wed, Oct 05, 2011 at 05:56:55PM -0700,  Junio C Hamano wrote:
> >> And this uses the gpg-interface.[ch] to allow signing the commit, i.e.
> >>
> >>     $ git commit --gpg-sign -m foo
> >>     You need a passphrase to unlock the secret key for
> >>     user: "Junio C Hamano <gitster@pobox.com>"
> >>     4096-bit RSA key, ID 96AFE6CB, created 2011-10-03 (main key ID 713660A7)
> >>
> >>     [master 8457d13] foo
> >>      1 files changed, 1 insertions(+), 0 deletions(-)
> > I like it, but I have a couple of questions: 
> > 1. Are the sig lines used in computed SHA1/commitid of a given commit (I
> >    see examples w/ --amend and that would usually change the SHA1)?
> Yes, just like with tag objects.
Ok, at the core, this is going to pose a problem with multiple
signatures.

Workflow example:
1. Dev1 creates a commit, signs it, pushes to central repo.
2. Dev2 pulls, signs the tip commit, pushes it back.

Since signing model here actually alters the commit, the push by Dev2
loses the history point of a commit with only a single signature, like
if somebody pushes a rewritten history (which should usually be
prohibited).

The push certificate variant of signing does permit this case without
breaking history.

> > I think this isn't a replacement for push certificates, but has value in
> > itself. It's certainly provides better integration than the
> > signature-in-note variants.
> > 
> 
> I do think it's meant as an implementation of push certificates. I don't
> see any other value in it which could not be achieved by signed tags.
> Can you describe any?
Identify of the committer for verification.

-- 
Robin Hugh Johnson
Gentoo Linux: Developer, Trustee & Infrastructure Lead
E-Mail     : robbat2@gentoo.org
GnuPG FP   : 11AC BA4F 4778 E3F6 E4ED  F38E B27B 944E 3488 4E85

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] commit: teach --gpg-sign option
  2011-10-09 22:57     ` Robin H. Johnson
@ 2011-10-09 23:18       ` Junio C Hamano
  2011-10-11  0:38         ` Robin H. Johnson
  0 siblings, 1 reply; 35+ messages in thread
From: Junio C Hamano @ 2011-10-09 23:18 UTC (permalink / raw)
  To: Robin H. Johnson; +Cc: Michael J Gruber, Git Mailing List, Junio C Hamano

"Robin H. Johnson" <robbat2@gentoo.org> writes:

> Workflow example:
> 1. Dev1 creates a commit, signs it, pushes to central repo.
> 2. Dev2 pulls, signs the tip commit, pushes it back.

I personally am not sympathetic to such a "sign every and all commits by
multiple people" workflow. If you really want to do such a thing, you can
have the second and subsequent one to create a new commit on top whose
sole purpose is to hold such a signature (commit --allow-empty --gpg-sig),
or use signed tags.

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] commit: teach --gpg-sign option
       [not found] ` <CACBZZX6xsnAv4S8zAqi08bcqrghZ8nKdzFP=UNCqZOqrEeLFnA@mail.gmail.com>
@ 2011-10-10  4:58   ` Junio C Hamano
  0 siblings, 0 replies; 35+ messages in thread
From: Junio C Hamano @ 2011-10-10  4:58 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: git

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

> A nit: no other commit header is an abbreviation, it would be more
> consistent to use "signature" instead of "sig".

You are probably right.

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] commit: teach --gpg-sign option
  2011-10-09 22:27   ` Junio C Hamano
@ 2011-10-10  6:33     ` Michael J Gruber
  2011-10-10 16:45       ` Junio C Hamano
  0 siblings, 1 reply; 35+ messages in thread
From: Michael J Gruber @ 2011-10-10  6:33 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Junio C Hamano venit, vidit, dixit 10.10.2011 00:27:
> Michael J Gruber <git@drmicha.warpmail.net> writes:
> 
>> BTW: commit --amend --gpg-sign strips an existing signature rather than
>> adding one. We might want the user to have a say here.
> 
> I think it deserves a separate command (commit --add-gpg-sign) that is
> used _only_ to add an additional signature by another person without
> affecting anything else in the commit (i.e. the tree, the parents and the
> author and committership information) from the viewpoint of the workflow,

Agreed, as I asked "the user to have a say".

> Obviously that "add-signature" mode needs to be aware of the existing
> signature. It is a deliberate design decision to strip existing signature
> when anything in the commit changes, which is the norm for --amend.

What norm? --amend keeps some header fields and discards others. In
fact, signing a commit "without changing it" (i.e. keeping tree, parents
etc., alias "--amend -C HEAD") should be the normal use case for signing
the tip of an existing branch. I mean, I have no problems adding to this:

git help fixup
`git fixup' is aliased to `commit --amend -C HEAD'

But what is the best default for the workflows that we encourage (commit
early, ...)? You answer a pull-request which happens to be a
fast-forward, sign the tip and suddenly you've taken over ownership (and
changed dates)??? Signing a commit should not do this.

Michael

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] commit: teach --gpg-sign option
  2011-10-09 21:22   ` Junio C Hamano
@ 2011-10-10  6:33     ` Michael J Gruber
  2011-10-10 16:35       ` Junio C Hamano
  0 siblings, 1 reply; 35+ messages in thread
From: Michael J Gruber @ 2011-10-10  6:33 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Junio C Hamano venit, vidit, dixit 09.10.2011 23:22:
> Michael J Gruber <git@drmicha.warpmail.net> writes:
> 
>> I just noticed that this format differs from the one of signed
>> tags. What special reason is there for the "sig " indentation?
> 
> Read the part of the message you are quoting.

I certainly did, and certainly did not find any mention. Do you think I
would have asked otherwise? I'm trying to be helpful by testing out a
patch in flight. That is: *I* am trying to be helpful.

This
> The lines of GPG detached signature are placed in new header lines, after
> the standard tree/parent/author/committer headers, instead of tucking the
> signature block at the end of the commit log message text (similar to how
> signed tag is done), for multiple reasons:
gave me the impression that commit signatures are done "similar to how
signed tag is done".

*After* doing several "cat-file" and after your insisting that you had
described the "sig " indent I come to the conclusion that you
implemented it this way "instead of... [doing it] similar to how signed
tag is done".

Before that, I misread those paragraphs (togetheter with the
non-existing object format doc) to mean that have a section in the
object which is ignored automatically, which is where tag signatures are
(while in fact they are not) and where commit signatures will go.

I have to say I dislike the fact that we would have different signature
formats. But I have spent too much time on this unnecessarily already.

Michael

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] commit: teach --gpg-sign option
  2011-10-10  6:33     ` Michael J Gruber
@ 2011-10-10 16:35       ` Junio C Hamano
  0 siblings, 0 replies; 35+ messages in thread
From: Junio C Hamano @ 2011-10-10 16:35 UTC (permalink / raw)
  To: Michael J Gruber; +Cc: git

Michael J Gruber <git@drmicha.warpmail.net> writes:

> Junio C Hamano venit, vidit, dixit 09.10.2011 23:22:
>> Michael J Gruber <git@drmicha.warpmail.net> writes:
>> 
>>> I just noticed that this format differs from the one of signed
>>> tags. What special reason is there for the "sig " indentation?
>> 
>> Read the part of the message you are quoting.
>
> I certainly did, and certainly did not find any mention. Do you think I
> would have asked otherwise? I'm trying to be helpful by testing out a
> patch in flight. That is: *I* am trying to be helpful.

Surely, sorry and thanks. I should have pointed out where I _thought_ I
spelled out the reason but apparently in an ineffective way (as my wording
did not convey what I wanted to say at least to you) a bit more clearly.

> This
>> The lines of GPG detached signature are placed in new header lines, after
>> the standard tree/parent/author/committer headers, instead of tucking the
>> signature block at the end of the commit log message text (similar to how
>> signed tag is done), for multiple reasons:
> gave me the impression that commit signatures are done "similar to how
> signed tag is done".

Yeah, sorry if that sentence parses badly.

It was trying to say:

 - the sig is in header lines
   - which is different (instead of) from tucking it at the end of text
     - by the way that "tucking at the end" would have been similar to
       signed tag

The reasons why signatures in the header is better than tucking at the end
are enumerated in the part you did not quote in this message but in the
part you did quote in the previous message.

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] commit: teach --gpg-sign option
  2011-10-10  6:33     ` Michael J Gruber
@ 2011-10-10 16:45       ` Junio C Hamano
  2011-10-11  6:39         ` Michael J Gruber
  0 siblings, 1 reply; 35+ messages in thread
From: Junio C Hamano @ 2011-10-10 16:45 UTC (permalink / raw)
  To: Michael J Gruber; +Cc: git

Michael J Gruber <git@drmicha.warpmail.net> writes:

> What norm? --amend keeps some header fields and discards others. In
> fact, signing a commit "without changing it" (i.e. keeping tree, parents
> etc., alias "--amend -C HEAD") should be the normal use case for signing
> the tip of an existing branch. I mean, I have no problems adding to this:
>
> git help fixup
> `git fixup' is aliased to `commit --amend -C HEAD'

You are *additionally* saying "-C HEAD" in an non-standard alias. Isn't
that enough indication that a vanila "--amend" is intended to record the
commit based on the updated context in which the new commit is made?
E.g. the authorship of the patch is still the same but committer
information is updated.

> But what is the best default for the workflows that we encourage (commit
> early, ...)? You answer a pull-request which happens to be a
> fast-forward, sign the tip and suddenly you've taken over ownership (and
> changed dates)??? Signing a commit should not do this.

I personally think a pull that is made in response to a pull-request,
i.e. the upstream merging from lieutenant, especially when the
authenticity of the puller matters, is perfectly fine with --no-ff.

Unlike the sign-less "we together made these history and nobody really
owns the result" (aka "Linus hates --no-ff merge because people do that to
leave a mark by peeing in the snow, without adding anything of value in
the history"), the whole purpose of signing a commit in the scenario you
mentioned is for the puller to leave his mark in the history.

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] commit: teach --gpg-sign option
  2011-10-09 23:18       ` Junio C Hamano
@ 2011-10-11  0:38         ` Robin H. Johnson
  0 siblings, 0 replies; 35+ messages in thread
From: Robin H. Johnson @ 2011-10-11  0:38 UTC (permalink / raw)
  To: Junio C Hamano, Git Mailing List; +Cc: 	Robin H. Johnson, Michael J Gruber

On Sun, Oct 09, 2011 at 04:18:49PM -0700,  Junio C Hamano wrote:
> "Robin H. Johnson" <robbat2@gentoo.org> writes:
> > Workflow example:
> > 1. Dev1 creates a commit, signs it, pushes to central repo.
> > 2. Dev2 pulls, signs the tip commit, pushes it back.
> 
> I personally am not sympathetic to such a "sign every and all commits by
> multiple people" workflow. If you really want to do such a thing, you can
> have the second and subsequent one to create a new commit on top whose
> sole purpose is to hold such a signature (commit --allow-empty --gpg-sig),
> or use signed tags.
For this case, I think having the push certificates works much better.

No easy solution to all of this, just lots of yak-shaving :-(.

-- 
Robin Hugh Johnson
Gentoo Linux: Developer, Trustee & Infrastructure Lead
E-Mail     : robbat2@gentoo.org
GnuPG FP   : 11AC BA4F 4778 E3F6 E4ED  F38E B27B 944E 3488 4E85

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH] commit: teach --gpg-sign option
  2011-10-10 16:45       ` Junio C Hamano
@ 2011-10-11  6:39         ` Michael J Gruber
  0 siblings, 0 replies; 35+ messages in thread
From: Michael J Gruber @ 2011-10-11  6:39 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Junio C Hamano venit, vidit, dixit 10.10.2011 18:45:
> Michael J Gruber <git@drmicha.warpmail.net> writes:
> 
>> What norm? --amend keeps some header fields and discards others. In
>> fact, signing a commit "without changing it" (i.e. keeping tree, parents
>> etc., alias "--amend -C HEAD") should be the normal use case for signing
>> the tip of an existing branch. I mean, I have no problems adding to this:
>>
>> git help fixup
>> `git fixup' is aliased to `commit --amend -C HEAD'
> 
> You are *additionally* saying "-C HEAD" in an non-standard alias. Isn't
> that enough indication that a vanila "--amend" is intended to record the
> commit based on the updated context in which the new commit is made?
> E.g. the authorship of the patch is still the same but committer
> information is updated.

I was more referring to leaving "parent" and "tree" headers in place,
which is a bit of a screwed comparison (because it relies on an
unchanged index).

>> But what is the best default for the workflows that we encourage (commit
>> early, ...)? You answer a pull-request which happens to be a
>> fast-forward, sign the tip and suddenly you've taken over ownership (and
>> changed dates)??? Signing a commit should not do this.
> 
> I personally think a pull that is made in response to a pull-request,
> i.e. the upstream merging from lieutenant, especially when the
> authenticity of the puller matters, is perfectly fine with --no-ff.

Yeah, --no-ff would work. I guess we should find out what our "main
customers" need here, since our own patch-based workflow is irrelevant,
and either approach works with our "single push authority". Seems
--no-ff is an easy solution to multiple sigs.

> Unlike the sign-less "we together made these history and nobody really
> owns the result" (aka "Linus hates --no-ff merge because people do that to
> leave a mark by peeing in the snow, without adding anything of value in
> the history"), the whole purpose of signing a commit in the scenario you
> mentioned is for the puller to leave his mark in the history.

diff --git i/Documentation/merge-options.txt
w/Documentation/merge-options.txt
index b613d4e..74d6a05 100644
--- i/Documentation/merge-options.txt
+++ w/Documentation/merge-options.txt
@@ -7,6 +7,13 @@ With --no-commit perform the merge but pretend the merge
 failed and do not autocommit, to give the user a chance to
 inspect and further tweak the merge result before committing.

+--pee::
+--no-pee::
+       This activates `--commit` and `--no-ff` and passes
+       `--gpg-sign` to `commit`.
++
+Use `--no-pee` if you do not want or need to pee.
+
 --ff::
 --no-ff::
        Do not generate a merge commit if the merge resolved as

:)

Michael

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v3 0/3] Signed-commit
  2011-10-06  0:56 [PATCH] commit: teach --gpg-sign option Junio C Hamano
                   ` (3 preceding siblings ...)
       [not found] ` <CACBZZX6xsnAv4S8zAqi08bcqrghZ8nKdzFP=UNCqZOqrEeLFnA@mail.gmail.com>
@ 2011-10-19  0:20 ` Junio C Hamano
  2011-10-19  0:20   ` [PATCH v3 1/3] Split GPG interface into its own helper library Junio C Hamano
                     ` (3 more replies)
  4 siblings, 4 replies; 35+ messages in thread
From: Junio C Hamano @ 2011-10-19  0:20 UTC (permalink / raw)
  To: git

This is another re-roll of the signed-commit topic.

 - The first patch refactors where the current code invokes gpg for
   signing and verification of tags;

 - The second patch introduces signed commit objects;

 - The third patch teaches "git log/show" to show the signature.

The internal API to drive "gpg" has been updated to better suit the
verification side, but I suspect that it needs another round of revamp to
slurp in the diagnostic message "gpg" produces to give us better control
of the formatting of the output. e.g.

    $ git show --format="%h %s%n%gpgsign" -s HEAD

might want to say:

    a030b7cf log: --show-signature
    RSA key ID 96AFE6CB, Good signature from "Junio C Hamano <gitster@pobox.com>"

or something like that, but the series is not there yet.

Junio C Hamano (3):
  Split GPG interface into its own helper library
  commit: teach --gpg-sign option
  log: --show-signature

 Makefile              |    2 +
 builtin/commit-tree.c |   24 ++++++++-
 builtin/commit.c      |   12 ++++-
 builtin/merge.c       |   16 +++++-
 builtin/tag.c         |   76 ++-------------------------
 builtin/verify-tag.c  |   36 ++-----------
 commit.c              |   74 ++++++++++++++++++++++++++-
 commit.h              |    5 ++-
 gpg-interface.c       |  137 +++++++++++++++++++++++++++++++++++++++++++++++++
 gpg-interface.h       |   10 ++++
 log-tree.c            |   17 ++++++
 notes-cache.c         |    2 +-
 notes-merge.c         |    2 +-
 revision.c            |    2 +
 revision.h            |    1 +
 tag.c                 |    5 ++
 16 files changed, 310 insertions(+), 111 deletions(-)
 create mode 100644 gpg-interface.c
 create mode 100644 gpg-interface.h

-- 
1.7.7.388.g3a4b7

^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH v3 1/3] Split GPG interface into its own helper library
  2011-10-19  0:20 ` [PATCH v3 0/3] Signed-commit Junio C Hamano
@ 2011-10-19  0:20   ` Junio C Hamano
  2011-10-19  0:20   ` [PATCH v3 2/3] commit: teach --gpg-sign option Junio C Hamano
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 35+ messages in thread
From: Junio C Hamano @ 2011-10-19  0:20 UTC (permalink / raw)
  To: git

This mostly moves existing code from builtin/tag.c (for signing)
and builtin/verify-tag.c (for verifying) to a new gpg-interface.c
file to provide a more generic library interface.

 - sign_buffer() takes a payload strbuf, a signature strbuf, and a signing
   key, runs "gpg" to produce a detached signature for the payload, and
   appends it to the signature strbuf. The contents of a signed tag that
   concatenates the payload and the detached signature can be produced by
   giving the same strbuf as payload and signature strbuf.

 - verify_signed_buffer() takes a payload and a detached signature as
   <ptr, len> pairs, and runs "gpg --verify" to see if the payload matches
   the signature.

"verify-tag" (aka "tag -v") saved the whole tag contents as if it is a
detached signature, and fed gpg the payload part of the tag. It relied on
gpg to fail when the given tag is not signed but just is annotated.  The
updated run_gpg_verify() function detects the lack of detached signature
in the input, and errors out without bothering "gpg".

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Makefile             |    2 +
 builtin/tag.c        |   76 ++-------------------------
 builtin/verify-tag.c |   36 ++-----------
 gpg-interface.c      |  137 ++++++++++++++++++++++++++++++++++++++++++++++++++
 gpg-interface.h      |   10 ++++
 tag.c                |    5 ++
 6 files changed, 166 insertions(+), 100 deletions(-)
 create mode 100644 gpg-interface.c
 create mode 100644 gpg-interface.h

diff --git a/Makefile b/Makefile
index 8d6d451..2183223 100644
--- a/Makefile
+++ b/Makefile
@@ -530,6 +530,7 @@ LIB_H += exec_cmd.h
 LIB_H += fsck.h
 LIB_H += gettext.h
 LIB_H += git-compat-util.h
+LIB_H += gpg-interface.h
 LIB_H += graph.h
 LIB_H += grep.h
 LIB_H += hash.h
@@ -620,6 +621,7 @@ LIB_OBJS += entry.o
 LIB_OBJS += environment.o
 LIB_OBJS += exec_cmd.o
 LIB_OBJS += fsck.o
+LIB_OBJS += gpg-interface.o
 LIB_OBJS += graph.o
 LIB_OBJS += grep.o
 LIB_OBJS += hash.o
diff --git a/builtin/tag.c b/builtin/tag.c
index 667515e..fb0d4a1 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -14,6 +14,7 @@
 #include "parse-options.h"
 #include "diff.h"
 #include "revision.h"
+#include "gpg-interface.h"
 
 static const char * const git_tag_usage[] = {
 	"git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]",
@@ -23,8 +24,6 @@ static const char * const git_tag_usage[] = {
 	NULL
 };
 
-static char signingkey[1000];
-
 struct tag_filter {
 	const char **patterns;
 	int lines;
@@ -208,60 +207,7 @@ static int verify_tag(const char *name, const char *ref,
 
 static int do_sign(struct strbuf *buffer)
 {
-	struct child_process gpg;
-	const char *args[4];
-	char *bracket;
-	int len;
-	int i, j;
-
-	if (!*signingkey) {
-		if (strlcpy(signingkey, git_committer_info(IDENT_ERROR_ON_NO_NAME),
-				sizeof(signingkey)) > sizeof(signingkey) - 1)
-			return error(_("committer info too long."));
-		bracket = strchr(signingkey, '>');
-		if (bracket)
-			bracket[1] = '\0';
-	}
-
-	/* When the username signingkey is bad, program could be terminated
-	 * because gpg exits without reading and then write gets SIGPIPE. */
-	signal(SIGPIPE, SIG_IGN);
-
-	memset(&gpg, 0, sizeof(gpg));
-	gpg.argv = args;
-	gpg.in = -1;
-	gpg.out = -1;
-	args[0] = "gpg";
-	args[1] = "-bsau";
-	args[2] = signingkey;
-	args[3] = NULL;
-
-	if (start_command(&gpg))
-		return error(_("could not run gpg."));
-
-	if (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) {
-		close(gpg.in);
-		close(gpg.out);
-		finish_command(&gpg);
-		return error(_("gpg did not accept the tag data"));
-	}
-	close(gpg.in);
-	len = strbuf_read(buffer, gpg.out, 1024);
-	close(gpg.out);
-
-	if (finish_command(&gpg) || !len || len < 0)
-		return error(_("gpg failed to sign the tag"));
-
-	/* Strip CR from the line endings, in case we are on Windows. */
-	for (i = j = 0; i < buffer->len; i++)
-		if (buffer->buf[i] != '\r') {
-			if (i != j)
-				buffer->buf[j] = buffer->buf[i];
-			j++;
-		}
-	strbuf_setlen(buffer, j);
-
-	return 0;
+	return sign_buffer(buffer, buffer, get_signing_key());
 }
 
 static const char tag_template[] =
@@ -270,21 +216,11 @@ static const char tag_template[] =
 	"# Write a tag message\n"
 	"#\n");
 
-static void set_signingkey(const char *value)
-{
-	if (strlcpy(signingkey, value, sizeof(signingkey)) >= sizeof(signingkey))
-		die(_("signing key value too long (%.10s...)"), value);
-}
-
 static int git_tag_config(const char *var, const char *value, void *cb)
 {
-	if (!strcmp(var, "user.signingkey")) {
-		if (!value)
-			return config_error_nonbool(var);
-		set_signingkey(value);
-		return 0;
-	}
-
+	int status = git_gpg_config(var, value, cb);
+	if (status)
+		return status;
 	return git_default_config(var, value, cb);
 }
 
@@ -463,7 +399,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 
 	if (keyid) {
 		sign = 1;
-		set_signingkey(keyid);
+		set_signing_key(keyid);
 	}
 	if (sign)
 		annotate = 1;
diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c
index 3134766..4bbcf77 100644
--- a/builtin/verify-tag.c
+++ b/builtin/verify-tag.c
@@ -11,6 +11,7 @@
 #include "run-command.h"
 #include <signal.h>
 #include "parse-options.h"
+#include "gpg-interface.h"
 
 static const char * const verify_tag_usage[] = {
 		"git verify-tag [-v|--verbose] <tag>...",
@@ -19,42 +20,17 @@ static const char * const verify_tag_usage[] = {
 
 static int run_gpg_verify(const char *buf, unsigned long size, int verbose)
 {
-	struct child_process gpg;
-	const char *args_gpg[] = {"gpg", "--verify", "FILE", "-", NULL};
-	char path[PATH_MAX];
-	size_t len;
-	int fd, ret;
+	int len;
 
-	fd = git_mkstemp(path, PATH_MAX, ".git_vtag_tmpXXXXXX");
-	if (fd < 0)
-		return error("could not create temporary file '%s': %s",
-						path, strerror(errno));
-	if (write_in_full(fd, buf, size) < 0)
-		return error("failed writing temporary file '%s': %s",
-						path, strerror(errno));
-	close(fd);
-
-	/* find the length without signature */
 	len = parse_signature(buf, size);
 	if (verbose)
 		write_in_full(1, buf, len);
 
-	memset(&gpg, 0, sizeof(gpg));
-	gpg.argv = args_gpg;
-	gpg.in = -1;
-	args_gpg[2] = path;
-	if (start_command(&gpg)) {
-		unlink(path);
-		return error("could not run gpg.");
-	}
-
-	write_in_full(gpg.in, buf, len);
-	close(gpg.in);
-	ret = finish_command(&gpg);
+	if (size == len)
+		return error("no signature found");
 
-	unlink_or_warn(path);
-
-	return ret;
+	return verify_signed_buffer(buf, len,
+				    buf + len, size - len, 0);
 }
 
 static int verify_tag(const char *name, int verbose)
diff --git a/gpg-interface.c b/gpg-interface.c
new file mode 100644
index 0000000..62f3806
--- /dev/null
+++ b/gpg-interface.c
@@ -0,0 +1,137 @@
+#include "cache.h"
+#include "run-command.h"
+#include "strbuf.h"
+#include "gpg-interface.h"
+#include "sigchain.h"
+
+static char *configured_signing_key;
+
+void set_signing_key(const char *key)
+{
+	free(configured_signing_key);
+	configured_signing_key = xstrdup(key);
+}
+
+int git_gpg_config(const char *var, const char *value, void *cb)
+{
+	if (!strcmp(var, "user.signingkey")) {
+		if (!value)
+			return config_error_nonbool(var);
+		set_signing_key(value);
+	}
+	return 0;
+}
+
+const char *get_signing_key(void)
+{
+	if (configured_signing_key)
+		return configured_signing_key;
+	return git_committer_info(IDENT_ERROR_ON_NO_NAME|IDENT_NO_DATE);
+}
+
+/*
+ * Create a detached signature for the contents of "buffer" and append
+ * it after "signature"; "buffer" and "signature" can be the same
+ * strbuf instance, which would cause the detached signature appended
+ * at the end.
+ */
+int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key)
+{
+	struct child_process gpg;
+	const char *args[4];
+	ssize_t len;
+	size_t i, j, bottom;
+
+	memset(&gpg, 0, sizeof(gpg));
+	gpg.argv = args;
+	gpg.in = -1;
+	gpg.out = -1;
+	args[0] = "gpg";
+	args[1] = "-bsau";
+	args[2] = signing_key;
+	args[3] = NULL;
+
+	if (start_command(&gpg))
+		return error(_("could not run gpg."));
+
+	/*
+	 * When the username signingkey is bad, program could be terminated
+	 * because gpg exits without reading and then write gets SIGPIPE.
+	 */
+	sigchain_push(SIGPIPE, SIG_IGN);
+
+	if (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) {
+		close(gpg.in);
+		close(gpg.out);
+		finish_command(&gpg);
+		return error(_("gpg did not accept the data"));
+	}
+	close(gpg.in);
+
+	bottom = signature->len;
+	len = strbuf_read(signature, gpg.out, 1024);
+	close(gpg.out);
+
+	sigchain_pop(SIGPIPE);
+
+	if (finish_command(&gpg) || !len || len < 0)
+		return error(_("gpg failed to sign the data"));
+
+	/* Strip CR from the line endings, in case we are on Windows. */
+	for (i = j = bottom; i < signature->len; i++)
+		if (signature->buf[i] != '\r') {
+			if (i != j)
+				signature->buf[j] = signature->buf[i];
+			j++;
+		}
+	strbuf_setlen(signature, j);
+
+	return 0;
+}
+
+/*
+ * Run "gpg" to see if the payload matches the detached signature.
+ * gpg_output_to tells where the output from "gpg" should go:
+ *   < 0: /dev/null
+ *   = 0: standard error of the calling process
+ *   > 0: the specified file descriptor
+ */
+int verify_signed_buffer(const char *payload, size_t payload_size,
+			 const char *signature, size_t signature_size,
+			 int gpg_output_to)
+{
+	struct child_process gpg;
+	const char *args_gpg[] = {"gpg", "--verify", "FILE", "-", NULL};
+	char path[PATH_MAX];
+	int fd, ret;
+
+	fd = git_mkstemp(path, PATH_MAX, ".git_vtag_tmpXXXXXX");
+	if (fd < 0)
+		return error("could not create temporary file '%s': %s",
+			     path, strerror(errno));
+	if (write_in_full(fd, signature, signature_size) < 0)
+		return error("failed writing detached signature to '%s': %s",
+			     path, strerror(errno));
+	close(fd);
+
+	memset(&gpg, 0, sizeof(gpg));
+	gpg.argv = args_gpg;
+	gpg.in = -1;
+	if (gpg_output_to < 0)
+		gpg.no_stderr = 1;
+	else
+		gpg.err = gpg_output_to;
+	args_gpg[2] = path;
+	if (start_command(&gpg)) {
+		unlink(path);
+		return error("could not run gpg.");
+	}
+
+	write_in_full(gpg.in, payload, payload_size);
+	close(gpg.in);
+	ret = finish_command(&gpg);
+
+	unlink_or_warn(path);
+
+	return ret;
+}
diff --git a/gpg-interface.h b/gpg-interface.h
new file mode 100644
index 0000000..df58bb9
--- /dev/null
+++ b/gpg-interface.h
@@ -0,0 +1,10 @@
+#ifndef GPG_INTERFACE_H
+#define GPG_INTERFACE_H
+
+extern int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key);
+extern int verify_signed_buffer(const char *payload, size_t payload_size, const char *signature, size_t signature_size, int gpg_output_to);
+extern int git_gpg_config(const char *, const char *, void *);
+extern void set_signing_key(const char *);
+extern const char *get_signing_key(void);
+
+#endif
diff --git a/tag.c b/tag.c
index 7d38cc0..3aa186d 100644
--- a/tag.c
+++ b/tag.c
@@ -139,6 +139,11 @@ int parse_tag(struct tag *item)
 	return ret;
 }
 
+/*
+ * Look at a signed tag object, and return the offset where
+ * the embedded detached signature begins, or the end of the
+ * data when there is no such signature.
+ */
 size_t parse_signature(const char *buf, unsigned long size)
 {
 	char *eol;
-- 
1.7.7.388.g3a4b7

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v3 2/3] commit: teach --gpg-sign option
  2011-10-19  0:20 ` [PATCH v3 0/3] Signed-commit Junio C Hamano
  2011-10-19  0:20   ` [PATCH v3 1/3] Split GPG interface into its own helper library Junio C Hamano
@ 2011-10-19  0:20   ` Junio C Hamano
  2011-10-19  0:20   ` [PATCH v3 3/3] log: --show-signature Junio C Hamano
  2011-10-20  0:36   ` [PATCH v4 0/5] Signed-commit Junio C Hamano
  3 siblings, 0 replies; 35+ messages in thread
From: Junio C Hamano @ 2011-10-19  0:20 UTC (permalink / raw)
  To: git

And this uses the gpg-interface.[ch] to allow signing the commit, i.e.

    $ git commit --gpg-sign -m foo
    You need a passphrase to unlock the secret key for
    user: "Junio C Hamano <gitster@pobox.com>"
    4096-bit RSA key, ID 96AFE6CB, created 2011-10-03 (main key ID 713660A7)

    [master 8457d13] foo
     1 files changed, 1 insertions(+), 0 deletions(-)

The lines of GPG detached signature are placed in new header lines, after
the standard tree/parent/author/committer headers, instead of tucking the
signature block at the end of the commit log message text (similar to how
signed tag is done), for multiple reasons:

 - The signature won't clutter output from "git log" and friends if it is
   in the extra header. If we place it at the end of the log message, we
   would need to teach "git log" and friends to strip the signature block
   with an option.

 - Teaching new versions of "git log" and "gitk" to optionally verify and
   show signatures is cleaner if we structurally know where the signature
   block is (instead of scanning in the commit log message).

 - The signature needs to be stripped upon various commit rewriting
   operations, e.g. rebase, filter-branch, etc. They all already ignore
   unknown headers, but if we place signature in the log message, all of
   these tools (and third-party tools) also need to learn how a signature
   block would look like.

 - When we added the optional encoding header, all the tools (both in tree
   and third-party) that acts on the raw commit object should have been
   fixed to ignore headers they do not understand, so it is not like that
   new header would be more likely to break than extra text in the commit.

A commit made with the above sample sequence would look like this:

    $ git cat-file commit HEAD
    tree 3cd71d90e3db4136e5260ab54599791c4f883b9d
    parent b87755351a47b09cb27d6913e6e0e17e6254a4d4
    author Junio C Hamano <gitster@pobox.com> 1317862251 -0700
    committer Junio C Hamano <gitster@pobox.com> 1317862251 -0700
    gpgsig -----BEGIN PGP SIGNATURE-----
    gpgsig Version: GnuPG v1.4.10 (GNU/Linux)
    gpgsig
    gpgsig iQIcBAABAgAGBQJOjPtrAAoJELC16IaWr+bL4TMP/RSe2Y/jYnCkds9unO5JEnfG
    gpgsig ...
    gpgsig =dt98
    gpgsig -----END PGP SIGNATURE-----

    foo

but "git log" (unless you ask for it with --pretty=raw) output is not
cluttered with the signature information.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---

 * The header field was updated from "sig" to "gpgsig" so that later we
   won't have to regret the choice when we introduce different kind of
   signature mechanism.

 builtin/commit-tree.c |   24 +++++++++++++++++++++---
 builtin/commit.c      |   12 ++++++++++--
 builtin/merge.c       |   16 ++++++++++++++--
 commit.c              |   40 +++++++++++++++++++++++++++++++++++++++-
 commit.h              |    2 +-
 notes-cache.c         |    2 +-
 notes-merge.c         |    2 +-
 7 files changed, 87 insertions(+), 11 deletions(-)

diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c
index d083795..a17811f 100644
--- a/builtin/commit-tree.c
+++ b/builtin/commit-tree.c
@@ -8,8 +8,9 @@
 #include "tree.h"
 #include "builtin.h"
 #include "utf8.h"
+#include "gpg-interface.h"
 
-static const char commit_tree_usage[] = "git commit-tree <sha1> [(-p <sha1>)...] < changelog";
+static const char commit_tree_usage[] = "git commit-tree [-S<signer>] <sha1> [(-p <sha1>)...] < changelog";
 
 static void new_parent(struct commit *parent, struct commit_list **parents_p)
 {
@@ -25,6 +26,14 @@ static void new_parent(struct commit *parent, struct commit_list **parents_p)
 	commit_list_insert(parent, parents_p);
 }
 
+static int commit_tree_config(const char *var, const char *value, void *cb)
+{
+	int status = git_gpg_config(var, value, NULL);
+	if (status)
+		return status;
+	return git_default_config(var, value, cb);
+}
+
 int cmd_commit_tree(int argc, const char **argv, const char *prefix)
 {
 	int i;
@@ -32,11 +41,19 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
 	unsigned char tree_sha1[20];
 	unsigned char commit_sha1[20];
 	struct strbuf buffer = STRBUF_INIT;
+	const char *sign_commit = NULL;
 
-	git_config(git_default_config, NULL);
+	git_config(commit_tree_config, NULL);
 
 	if (argc < 2 || !strcmp(argv[1], "-h"))
 		usage(commit_tree_usage);
+
+	if (!memcmp(argv[1], "-S", 2)) {
+		sign_commit = argv[1] + 2;
+		argv++;
+		argc--;
+	}
+
 	if (get_sha1(argv[1], tree_sha1))
 		die("Not a valid object name %s", argv[1]);
 
@@ -56,7 +73,8 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
 	if (strbuf_read(&buffer, 0, 0) < 0)
 		die_errno("git commit-tree: failed to read");
 
-	if (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) {
+	if (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1,
+			NULL, sign_commit)) {
 		strbuf_release(&buffer);
 		return 1;
 	}
diff --git a/builtin/commit.c b/builtin/commit.c
index cbc9613..90cf7e8 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -26,6 +26,7 @@
 #include "unpack-trees.h"
 #include "quote.h"
 #include "submodule.h"
+#include "gpg-interface.h"
 
 static const char * const builtin_commit_usage[] = {
 	"git commit [options] [--] <filepattern>...",
@@ -85,6 +86,8 @@ static int all, edit_flag, also, interactive, patch_interactive, only, amend, si
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 static int no_post_rewrite, allow_empty_message;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
+static char *sign_commit;
+
 /*
  * The default commit message cleanup mode will remove the lines
  * beginning with # (shell comments) and leading and trailing
@@ -144,6 +147,8 @@ static struct option builtin_commit_options[] = {
 	OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
 	OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
 	OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"),
+	{ OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id",
+	  "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
 	/* end commit message options */
 
 	OPT_GROUP("Commit contents options"),
@@ -1323,6 +1328,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1,
 static int git_commit_config(const char *k, const char *v, void *cb)
 {
 	struct wt_status *s = cb;
+	int status;
 
 	if (!strcmp(k, "commit.template"))
 		return git_config_pathname(&template_file, k, v);
@@ -1330,7 +1336,9 @@ static int git_commit_config(const char *k, const char *v, void *cb)
 		include_status = git_config_bool(k, v);
 		return 0;
 	}
-
+	status = git_gpg_config(k, v, NULL);
+	if (status)
+		return status;
 	return git_status_config(k, v, s);
 }
 
@@ -1481,7 +1489,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 	}
 
 	if (commit_tree(sb.buf, active_cache_tree->sha1, parents, sha1,
-			author_ident.buf)) {
+			author_ident.buf, sign_commit)) {
 		rollback_index_files();
 		die(_("failed to write commit object"));
 	}
diff --git a/builtin/merge.c b/builtin/merge.c
index ab4077f..53cff02 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -26,6 +26,7 @@
 #include "merge-recursive.h"
 #include "resolve-undo.h"
 #include "remote.h"
+#include "gpg-interface.h"
 
 #define DEFAULT_TWOHEAD (1<<0)
 #define DEFAULT_OCTOPUS (1<<1)
@@ -63,6 +64,7 @@ static int allow_rerere_auto;
 static int abort_current_merge;
 static int show_progress = -1;
 static int default_to_upstream;
+static const char *sign_commit;
 
 static struct strategy all_strategy[] = {
 	{ "recursive",  DEFAULT_TWOHEAD | NO_TRIVIAL },
@@ -206,6 +208,8 @@ static struct option builtin_merge_options[] = {
 	OPT_BOOLEAN(0, "abort", &abort_current_merge,
 		"abort the current in-progress merge"),
 	OPT_SET_INT(0, "progress", &show_progress, "force progress reporting", 1),
+	{ OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id",
+	  "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
 	OPT_END()
 };
 
@@ -525,6 +529,8 @@ static void parse_branch_merge_options(char *bmo)
 
 static int git_merge_config(const char *k, const char *v, void *cb)
 {
+	int status;
+
 	if (branch && !prefixcmp(k, "branch.") &&
 		!prefixcmp(k + 7, branch) &&
 		!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
@@ -562,6 +568,10 @@ static int git_merge_config(const char *k, const char *v, void *cb)
 		default_to_upstream = git_config_bool(k, v);
 		return 0;
 	}
+
+	status = git_gpg_config(k, v, NULL);
+	if (status)
+		return status;
 	return git_diff_ui_config(k, v, cb);
 }
 
@@ -870,7 +880,8 @@ static int merge_trivial(void)
 	parent->next->item = remoteheads->item;
 	parent->next->next = NULL;
 	run_prepare_commit_msg();
-	commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
+	commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL,
+		    sign_commit);
 	finish(result_commit, "In-index merge");
 	drop_save();
 	return 0;
@@ -900,7 +911,8 @@ static int finish_automerge(struct commit_list *common,
 	free_commit_list(remoteheads);
 	strbuf_addch(&merge_msg, '\n');
 	run_prepare_commit_msg();
-	commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
+	commit_tree(merge_msg.buf, result_tree, parents, result_commit,
+		    NULL, sign_commit);
 	strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
 	finish(result_commit, buf.buf);
 	strbuf_release(&buf);
diff --git a/commit.c b/commit.c
index 97b4327..4bff3cd 100644
--- a/commit.c
+++ b/commit.c
@@ -6,6 +6,7 @@
 #include "diff.h"
 #include "revision.h"
 #include "notes.h"
+#include "gpg-interface.h"
 
 int save_commit_buffer = 1;
 
@@ -814,6 +815,40 @@ struct commit_list *reduce_heads(struct commit_list *heads)
 	return result;
 }
 
+static const char gpg_sig_header[] = "gpgsig ";
+static const int gpg_sig_header_len = sizeof(gpg_sig_header) - 1;
+
+static int do_sign_commit(struct strbuf *buf, const char *keyid)
+{
+	struct strbuf sig = STRBUF_INIT;
+	int inspos, copypos;
+
+	/* find the end of the header */
+	inspos = strstr(buf->buf, "\n\n") - buf->buf + 1;
+
+	if (!keyid || !*keyid)
+		keyid = get_signing_key();
+	if (sign_buffer(buf, &sig, keyid)) {
+		strbuf_release(&sig);
+		return -1;
+	}
+
+	for (copypos = 0; sig.buf[copypos]; ) {
+		const char *bol = sig.buf + copypos;
+		const char *eol = strchrnul(bol, '\n');
+		int len = (eol - bol) + !!*eol;
+
+		strbuf_insert(buf, inspos, gpg_sig_header, gpg_sig_header_len);
+		inspos += gpg_sig_header_len;
+		strbuf_insert(buf, inspos, bol, len);
+		inspos += len;
+		copypos += len;
+	}
+	strbuf_release(&sig);
+	return 0;
+}
+
+
 static const char commit_utf8_warn[] =
 "Warning: commit message does not conform to UTF-8.\n"
 "You may want to amend it after fixing the message, or set the config\n"
@@ -821,7 +856,7 @@ static const char commit_utf8_warn[] =
 
 int commit_tree(const char *msg, unsigned char *tree,
 		struct commit_list *parents, unsigned char *ret,
-		const char *author)
+		const char *author, const char *sign_commit)
 {
 	int result;
 	int encoding_is_utf8;
@@ -864,6 +899,9 @@ int commit_tree(const char *msg, unsigned char *tree,
 	if (encoding_is_utf8 && !is_utf8(buffer.buf))
 		fprintf(stderr, commit_utf8_warn);
 
+	if (sign_commit && do_sign_commit(&buffer, sign_commit))
+		return -1;
+
 	result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
 	strbuf_release(&buffer);
 	return result;
diff --git a/commit.h b/commit.h
index 12d100b8..8c2419b 100644
--- a/commit.h
+++ b/commit.h
@@ -175,6 +175,6 @@ struct commit_list *reduce_heads(struct commit_list *heads);
 
 extern int commit_tree(const char *msg, unsigned char *tree,
 		struct commit_list *parents, unsigned char *ret,
-		const char *author);
+		       const char *author, const char *sign_commit);
 
 #endif /* COMMIT_H */
diff --git a/notes-cache.c b/notes-cache.c
index 4c8984e..c36a960 100644
--- a/notes-cache.c
+++ b/notes-cache.c
@@ -56,7 +56,7 @@ int notes_cache_write(struct notes_cache *c)
 
 	if (write_notes_tree(&c->tree, tree_sha1))
 		return -1;
-	if (commit_tree(c->validity, tree_sha1, NULL, commit_sha1, NULL) < 0)
+	if (commit_tree(c->validity, tree_sha1, NULL, commit_sha1, NULL, NULL) < 0)
 		return -1;
 	if (update_ref("update notes cache", c->tree.ref, commit_sha1, NULL,
 		       0, QUIET_ON_ERR) < 0)
diff --git a/notes-merge.c b/notes-merge.c
index e1aaf43..c29c434 100644
--- a/notes-merge.c
+++ b/notes-merge.c
@@ -546,7 +546,7 @@ void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
 		/* else: t->ref points to nothing, assume root/orphan commit */
 	}
 
-	if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL))
+	if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL, NULL))
 		die("Failed to commit notes tree to database");
 }
 
-- 
1.7.7.388.g3a4b7

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v3 3/3] log: --show-signature
  2011-10-19  0:20 ` [PATCH v3 0/3] Signed-commit Junio C Hamano
  2011-10-19  0:20   ` [PATCH v3 1/3] Split GPG interface into its own helper library Junio C Hamano
  2011-10-19  0:20   ` [PATCH v3 2/3] commit: teach --gpg-sign option Junio C Hamano
@ 2011-10-19  0:20   ` Junio C Hamano
  2011-10-20  0:36   ` [PATCH v4 0/5] Signed-commit Junio C Hamano
  3 siblings, 0 replies; 35+ messages in thread
From: Junio C Hamano @ 2011-10-19  0:20 UTC (permalink / raw)
  To: git

This teaches the "log" family of commands to pass the GPG signature in the
commit objects to "gpg --verify" via the verify_signed_buffer() interface
used to verify signed tag objects. E.g.

    $ git show --show-signature -s HEAD

would show GPG output in the header part of the output.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---

 * We may want to capture good/bad/untrusted signature information from
   GPG and teach userformat to show that, but this code is not there yet.

 commit.c   |   34 ++++++++++++++++++++++++++++++++++
 commit.h   |    3 +++
 log-tree.c |   17 +++++++++++++++++
 revision.c |    2 ++
 revision.h |    1 +
 5 files changed, 57 insertions(+), 0 deletions(-)

diff --git a/commit.c b/commit.c
index 4bff3cd..93045a2 100644
--- a/commit.c
+++ b/commit.c
@@ -848,6 +848,40 @@ static int do_sign_commit(struct strbuf *buf, const char *keyid)
 	return 0;
 }
 
+int parse_signed_commit(const unsigned char *sha1,
+			struct strbuf *payload, struct strbuf *signature)
+{
+	unsigned long size;
+	enum object_type type;
+	char *buffer = read_sha1_file(sha1, &type, &size);
+	int in_header, saw_signature = -1;
+	char *line;
+
+	if (!buffer || type != OBJ_COMMIT)
+		goto cleanup;
+
+	line = buffer;
+	in_header = 1;
+	saw_signature = 0;
+	while (*line) {
+		char *next = strchrnul(line, '\n');
+		if (*next)
+			next++;
+		if (in_header && !prefixcmp(line, gpg_sig_header)) {
+			const char *sig = line + gpg_sig_header_len;
+			strbuf_add(signature, sig, next - sig);
+			saw_signature = 1;
+		} else {
+			strbuf_add(payload, line, next - line);
+		}
+		if (*line == '\n')
+			in_header = 0;
+		line = next;
+	}
+ cleanup:
+	free(buffer);
+	return saw_signature;
+}
 
 static const char commit_utf8_warn[] =
 "Warning: commit message does not conform to UTF-8.\n"
diff --git a/commit.h b/commit.h
index 8c2419b..1885471 100644
--- a/commit.h
+++ b/commit.h
@@ -177,4 +177,7 @@ extern int commit_tree(const char *msg, unsigned char *tree,
 		struct commit_list *parents, unsigned char *ret,
 		       const char *author, const char *sign_commit);
 
+extern int parse_signed_commit(const unsigned char *sha1,
+			       struct strbuf *message, struct strbuf *signature);
+
 #endif /* COMMIT_H */
diff --git a/log-tree.c b/log-tree.c
index 24c295e..749bb65 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -8,6 +8,7 @@
 #include "refs.h"
 #include "string-list.h"
 #include "color.h"
+#include "gpg-interface.h"
 
 struct decoration name_decoration = { "object names" };
 
@@ -395,6 +396,19 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit,
 	*extra_headers_p = extra_headers;
 }
 
+static void show_signature(struct rev_info *opt, struct commit *commit)
+{
+	struct strbuf payload = STRBUF_INIT;
+	struct strbuf signature = STRBUF_INIT;
+
+	if (parse_signed_commit(commit->object.sha1, &payload, &signature) >= 0) {
+		verify_signed_buffer(payload.buf, payload.len,
+				     signature.buf, signature.len, 0);
+	}
+	strbuf_release(&payload);
+	strbuf_release(&signature);
+}
+
 void show_log(struct rev_info *opt)
 {
 	struct strbuf msgbuf = STRBUF_INIT;
@@ -502,6 +516,9 @@ void show_log(struct rev_info *opt)
 		}
 	}
 
+	if (opt->show_signature)
+		show_signature(opt, commit);
+
 	if (!commit->buffer)
 		return;
 
diff --git a/revision.c b/revision.c
index c46cfaa..860a312 100644
--- a/revision.c
+++ b/revision.c
@@ -1381,6 +1381,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
 		revs->show_notes = 1;
 		revs->show_notes_given = 1;
 		revs->notes_opt.use_default_notes = 1;
+	} else if (!strcmp(arg, "--show-signature")) {
+		revs->show_signature = 1;
 	} else if (!prefixcmp(arg, "--show-notes=") ||
 		   !prefixcmp(arg, "--notes=")) {
 		struct strbuf buf = STRBUF_INIT;
diff --git a/revision.h b/revision.h
index 3d64ada..198bb95 100644
--- a/revision.h
+++ b/revision.h
@@ -89,6 +89,7 @@ struct rev_info {
 			show_merge:1,
 			show_notes:1,
 			show_notes_given:1,
+			show_signature:1,
 			pretty_given:1,
 			abbrev_commit:1,
 			abbrev_commit_given:1,
-- 
1.7.7.388.g3a4b7

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v4 0/5] Signed-commit
  2011-10-19  0:20 ` [PATCH v3 0/3] Signed-commit Junio C Hamano
                     ` (2 preceding siblings ...)
  2011-10-19  0:20   ` [PATCH v3 3/3] log: --show-signature Junio C Hamano
@ 2011-10-20  0:36   ` Junio C Hamano
  2011-10-20  0:36     ` [PATCH v4 1/5] Split GPG interface into its own helper library Junio C Hamano
                       ` (5 more replies)
  3 siblings, 6 replies; 35+ messages in thread
From: Junio C Hamano @ 2011-10-20  0:36 UTC (permalink / raw)
  To: git

The fourth iteration.

 - The first patch refactors where the current code invokes gpg for
   signing and verification of tags;

 - The second patch introduces signed commit objects (unchanged);

 - The third patch teaches "git log/show" to show the signature (adjusted
   to the updated "gpg output capture" API, with colors);

 - The fourth patch refactors helper bits to test GPG related features
   out of existing "tag -s" && "tag --verify" test script (new); and

 - The fifth patch adds tests for "commit [--amend] -S", "merge -S",
   and "show --show-signature" (new).

I think the internal API to drive "gpg" in this iteration is good to go.
The verification side has been updated to allow the callers to capture
output from GPG in a strbuf, so that they can pretty-print it in a format
more suitable in their context, rather than just spewing it out to the
standard error stream.

Junio C Hamano (5):
  Split GPG interface into its own helper library
  commit: teach --gpg-sign option
  log: --show-signature
  t7004: extract generic "GPG testing" bits
  test "commit -S" and "log --show-signature"

 Makefile                 |    2 +
 builtin/commit-tree.c    |   24 +++++++-
 builtin/commit.c         |   12 +++-
 builtin/merge.c          |   16 +++++-
 builtin/tag.c            |   76 ++-----------------------
 builtin/verify-tag.c     |   35 ++----------
 commit.c                 |   74 ++++++++++++++++++++++++-
 commit.h                 |    5 +-
 gpg-interface.c          |  138 ++++++++++++++++++++++++++++++++++++++++++++++
 gpg-interface.h          |   10 +++
 log-tree.c               |   39 +++++++++++++
 notes-cache.c            |    2 +-
 notes-merge.c            |    2 +-
 revision.c               |    2 +
 revision.h               |    1 +
 t/lib-gpg.sh             |   29 ++++++++++
 t/lib-gpg/pubring.gpg    |  Bin 0 -> 1164 bytes
 t/lib-gpg/random_seed    |  Bin 0 -> 600 bytes
 t/lib-gpg/secring.gpg    |  Bin 0 -> 1237 bytes
 t/lib-gpg/trustdb.gpg    |  Bin 0 -> 1280 bytes
 t/t7004-tag.sh           |   29 +---------
 t/t7004/pubring.gpg      |  Bin 1164 -> 0 bytes
 t/t7004/random_seed      |  Bin 600 -> 0 bytes
 t/t7004/secring.gpg      |  Bin 1237 -> 0 bytes
 t/t7004/trustdb.gpg      |  Bin 1280 -> 0 bytes
 t/t7510-signed-commit.sh |   60 ++++++++++++++++++++
 tag.c                    |    5 ++
 27 files changed, 422 insertions(+), 139 deletions(-)
 create mode 100644 gpg-interface.c
 create mode 100644 gpg-interface.h
 create mode 100644 t/lib-gpg.sh
 create mode 100644 t/lib-gpg/pubring.gpg
 create mode 100644 t/lib-gpg/random_seed
 create mode 100644 t/lib-gpg/secring.gpg
 create mode 100644 t/lib-gpg/trustdb.gpg
 delete mode 100644 t/t7004/pubring.gpg
 delete mode 100644 t/t7004/random_seed
 delete mode 100644 t/t7004/secring.gpg
 delete mode 100644 t/t7004/trustdb.gpg
 create mode 100755 t/t7510-signed-commit.sh

-- 
1.7.7.498.g3f2e50

^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH v4 1/5] Split GPG interface into its own helper library
  2011-10-20  0:36   ` [PATCH v4 0/5] Signed-commit Junio C Hamano
@ 2011-10-20  0:36     ` Junio C Hamano
  2011-10-20  0:37     ` [PATCH v4 2/5] commit: teach --gpg-sign option Junio C Hamano
                       ` (4 subsequent siblings)
  5 siblings, 0 replies; 35+ messages in thread
From: Junio C Hamano @ 2011-10-20  0:36 UTC (permalink / raw)
  To: git

This mostly moves existing code from builtin/tag.c (for signing)
and builtin/verify-tag.c (for verifying) to a new gpg-interface.c
file to provide a more generic library interface.

 - sign_buffer() takes a payload strbuf, a signature strbuf, and a signing
   key, runs "gpg" to produce a detached signature for the payload, and
   appends it to the signature strbuf. The contents of a signed tag that
   concatenates the payload and the detached signature can be produced by
   giving the same strbuf as payload and signature strbuf.

 - verify_signed_buffer() takes a payload and a detached signature as
   <ptr, len> pairs, and runs "gpg --verify" to see if the payload matches
   the signature. It can optionally capture the output from GPG to allow
   the callers to pretty-print it in a way more suitable for their
   contexts.

"verify-tag" (aka "tag -v") used to save the whole tag contents as if it
is a detached signature, and fed gpg the payload part of the tag. It
relied on gpg to fail when the given tag is not signed but just is
annotated.  The updated run_gpg_verify() function detects the lack of
detached signature in the input, and errors out without bothering "gpg".

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Makefile             |    2 +
 builtin/tag.c        |   76 ++-------------------------
 builtin/verify-tag.c |   35 ++-----------
 gpg-interface.c      |  138 ++++++++++++++++++++++++++++++++++++++++++++++++++
 gpg-interface.h      |   10 ++++
 tag.c                |    5 ++
 6 files changed, 166 insertions(+), 100 deletions(-)
 create mode 100644 gpg-interface.c
 create mode 100644 gpg-interface.h

diff --git a/Makefile b/Makefile
index 8d6d451..2183223 100644
--- a/Makefile
+++ b/Makefile
@@ -530,6 +530,7 @@ LIB_H += exec_cmd.h
 LIB_H += fsck.h
 LIB_H += gettext.h
 LIB_H += git-compat-util.h
+LIB_H += gpg-interface.h
 LIB_H += graph.h
 LIB_H += grep.h
 LIB_H += hash.h
@@ -620,6 +621,7 @@ LIB_OBJS += entry.o
 LIB_OBJS += environment.o
 LIB_OBJS += exec_cmd.o
 LIB_OBJS += fsck.o
+LIB_OBJS += gpg-interface.o
 LIB_OBJS += graph.o
 LIB_OBJS += grep.o
 LIB_OBJS += hash.o
diff --git a/builtin/tag.c b/builtin/tag.c
index 667515e..fb0d4a1 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -14,6 +14,7 @@
 #include "parse-options.h"
 #include "diff.h"
 #include "revision.h"
+#include "gpg-interface.h"
 
 static const char * const git_tag_usage[] = {
 	"git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]",
@@ -23,8 +24,6 @@ static const char * const git_tag_usage[] = {
 	NULL
 };
 
-static char signingkey[1000];
-
 struct tag_filter {
 	const char **patterns;
 	int lines;
@@ -208,60 +207,7 @@ static int verify_tag(const char *name, const char *ref,
 
 static int do_sign(struct strbuf *buffer)
 {
-	struct child_process gpg;
-	const char *args[4];
-	char *bracket;
-	int len;
-	int i, j;
-
-	if (!*signingkey) {
-		if (strlcpy(signingkey, git_committer_info(IDENT_ERROR_ON_NO_NAME),
-				sizeof(signingkey)) > sizeof(signingkey) - 1)
-			return error(_("committer info too long."));
-		bracket = strchr(signingkey, '>');
-		if (bracket)
-			bracket[1] = '\0';
-	}
-
-	/* When the username signingkey is bad, program could be terminated
-	 * because gpg exits without reading and then write gets SIGPIPE. */
-	signal(SIGPIPE, SIG_IGN);
-
-	memset(&gpg, 0, sizeof(gpg));
-	gpg.argv = args;
-	gpg.in = -1;
-	gpg.out = -1;
-	args[0] = "gpg";
-	args[1] = "-bsau";
-	args[2] = signingkey;
-	args[3] = NULL;
-
-	if (start_command(&gpg))
-		return error(_("could not run gpg."));
-
-	if (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) {
-		close(gpg.in);
-		close(gpg.out);
-		finish_command(&gpg);
-		return error(_("gpg did not accept the tag data"));
-	}
-	close(gpg.in);
-	len = strbuf_read(buffer, gpg.out, 1024);
-	close(gpg.out);
-
-	if (finish_command(&gpg) || !len || len < 0)
-		return error(_("gpg failed to sign the tag"));
-
-	/* Strip CR from the line endings, in case we are on Windows. */
-	for (i = j = 0; i < buffer->len; i++)
-		if (buffer->buf[i] != '\r') {
-			if (i != j)
-				buffer->buf[j] = buffer->buf[i];
-			j++;
-		}
-	strbuf_setlen(buffer, j);
-
-	return 0;
+	return sign_buffer(buffer, buffer, get_signing_key());
 }
 
 static const char tag_template[] =
@@ -270,21 +216,11 @@ static const char tag_template[] =
 	"# Write a tag message\n"
 	"#\n");
 
-static void set_signingkey(const char *value)
-{
-	if (strlcpy(signingkey, value, sizeof(signingkey)) >= sizeof(signingkey))
-		die(_("signing key value too long (%.10s...)"), value);
-}
-
 static int git_tag_config(const char *var, const char *value, void *cb)
 {
-	if (!strcmp(var, "user.signingkey")) {
-		if (!value)
-			return config_error_nonbool(var);
-		set_signingkey(value);
-		return 0;
-	}
-
+	int status = git_gpg_config(var, value, cb);
+	if (status)
+		return status;
 	return git_default_config(var, value, cb);
 }
 
@@ -463,7 +399,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
 
 	if (keyid) {
 		sign = 1;
-		set_signingkey(keyid);
+		set_signing_key(keyid);
 	}
 	if (sign)
 		annotate = 1;
diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c
index 3134766..28c2174 100644
--- a/builtin/verify-tag.c
+++ b/builtin/verify-tag.c
@@ -11,6 +11,7 @@
 #include "run-command.h"
 #include <signal.h>
 #include "parse-options.h"
+#include "gpg-interface.h"
 
 static const char * const verify_tag_usage[] = {
 		"git verify-tag [-v|--verbose] <tag>...",
@@ -19,42 +20,16 @@ static const char * const verify_tag_usage[] = {
 
 static int run_gpg_verify(const char *buf, unsigned long size, int verbose)
 {
-	struct child_process gpg;
-	const char *args_gpg[] = {"gpg", "--verify", "FILE", "-", NULL};
-	char path[PATH_MAX];
-	size_t len;
-	int fd, ret;
+	int len;
 
-	fd = git_mkstemp(path, PATH_MAX, ".git_vtag_tmpXXXXXX");
-	if (fd < 0)
-		return error("could not create temporary file '%s': %s",
-						path, strerror(errno));
-	if (write_in_full(fd, buf, size) < 0)
-		return error("failed writing temporary file '%s': %s",
-						path, strerror(errno));
-	close(fd);
-
-	/* find the length without signature */
 	len = parse_signature(buf, size);
 	if (verbose)
 		write_in_full(1, buf, len);
 
-	memset(&gpg, 0, sizeof(gpg));
-	gpg.argv = args_gpg;
-	gpg.in = -1;
-	args_gpg[2] = path;
-	if (start_command(&gpg)) {
-		unlink(path);
-		return error("could not run gpg.");
-	}
-
-	write_in_full(gpg.in, buf, len);
-	close(gpg.in);
-	ret = finish_command(&gpg);
+	if (size == len)
+		return error("no signature found");
 
-	unlink_or_warn(path);
-
-	return ret;
+	return verify_signed_buffer(buf, len, buf + len, size - len, NULL);
 }
 
 static int verify_tag(const char *name, int verbose)
diff --git a/gpg-interface.c b/gpg-interface.c
new file mode 100644
index 0000000..ff232c8
--- /dev/null
+++ b/gpg-interface.c
@@ -0,0 +1,138 @@
+#include "cache.h"
+#include "run-command.h"
+#include "strbuf.h"
+#include "gpg-interface.h"
+#include "sigchain.h"
+
+static char *configured_signing_key;
+
+void set_signing_key(const char *key)
+{
+	free(configured_signing_key);
+	configured_signing_key = xstrdup(key);
+}
+
+int git_gpg_config(const char *var, const char *value, void *cb)
+{
+	if (!strcmp(var, "user.signingkey")) {
+		if (!value)
+			return config_error_nonbool(var);
+		set_signing_key(value);
+	}
+	return 0;
+}
+
+const char *get_signing_key(void)
+{
+	if (configured_signing_key)
+		return configured_signing_key;
+	return git_committer_info(IDENT_ERROR_ON_NO_NAME|IDENT_NO_DATE);
+}
+
+/*
+ * Create a detached signature for the contents of "buffer" and append
+ * it after "signature"; "buffer" and "signature" can be the same
+ * strbuf instance, which would cause the detached signature appended
+ * at the end.
+ */
+int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key)
+{
+	struct child_process gpg;
+	const char *args[4];
+	ssize_t len;
+	size_t i, j, bottom;
+
+	memset(&gpg, 0, sizeof(gpg));
+	gpg.argv = args;
+	gpg.in = -1;
+	gpg.out = -1;
+	args[0] = "gpg";
+	args[1] = "-bsau";
+	args[2] = signing_key;
+	args[3] = NULL;
+
+	if (start_command(&gpg))
+		return error(_("could not run gpg."));
+
+	/*
+	 * When the username signingkey is bad, program could be terminated
+	 * because gpg exits without reading and then write gets SIGPIPE.
+	 */
+	sigchain_push(SIGPIPE, SIG_IGN);
+
+	if (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) {
+		close(gpg.in);
+		close(gpg.out);
+		finish_command(&gpg);
+		return error(_("gpg did not accept the data"));
+	}
+	close(gpg.in);
+
+	bottom = signature->len;
+	len = strbuf_read(signature, gpg.out, 1024);
+	close(gpg.out);
+
+	sigchain_pop(SIGPIPE);
+
+	if (finish_command(&gpg) || !len || len < 0)
+		return error(_("gpg failed to sign the data"));
+
+	/* Strip CR from the line endings, in case we are on Windows. */
+	for (i = j = bottom; i < signature->len; i++)
+		if (signature->buf[i] != '\r') {
+			if (i != j)
+				signature->buf[j] = signature->buf[i];
+			j++;
+		}
+	strbuf_setlen(signature, j);
+
+	return 0;
+}
+
+/*
+ * Run "gpg" to see if the payload matches the detached signature.
+ * gpg_output_to tells where the output from "gpg" should go:
+ *   < 0: /dev/null
+ *   = 0: standard error of the calling process
+ *   > 0: the specified file descriptor
+ */
+int verify_signed_buffer(const char *payload, size_t payload_size,
+			 const char *signature, size_t signature_size,
+			 struct strbuf *gpg_output)
+{
+	struct child_process gpg;
+	const char *args_gpg[] = {"gpg", "--verify", "FILE", "-", NULL};
+	char path[PATH_MAX];
+	int fd, ret;
+
+	fd = git_mkstemp(path, PATH_MAX, ".git_vtag_tmpXXXXXX");
+	if (fd < 0)
+		return error("could not create temporary file '%s': %s",
+			     path, strerror(errno));
+	if (write_in_full(fd, signature, signature_size) < 0)
+		return error("failed writing detached signature to '%s': %s",
+			     path, strerror(errno));
+	close(fd);
+
+	memset(&gpg, 0, sizeof(gpg));
+	gpg.argv = args_gpg;
+	gpg.in = -1;
+	if (gpg_output)
+		gpg.err = -1;
+	args_gpg[2] = path;
+	if (start_command(&gpg)) {
+		unlink(path);
+		return error("could not run gpg.");
+	}
+
+	write_in_full(gpg.in, payload, payload_size);
+	close(gpg.in);
+
+	if (gpg_output)
+		strbuf_read(gpg_output, gpg.err, 0);
+	ret = finish_command(&gpg);
+
+	unlink_or_warn(path);
+
+	return ret;
+}
diff --git a/gpg-interface.h b/gpg-interface.h
new file mode 100644
index 0000000..b9c3608
--- /dev/null
+++ b/gpg-interface.h
@@ -0,0 +1,10 @@
+#ifndef GPG_INTERFACE_H
+#define GPG_INTERFACE_H
+
+extern int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key);
+extern int verify_signed_buffer(const char *payload, size_t payload_size, const char *signature, size_t signature_size, struct strbuf *gpg_output);
+extern int git_gpg_config(const char *, const char *, void *);
+extern void set_signing_key(const char *);
+extern const char *get_signing_key(void);
+
+#endif
diff --git a/tag.c b/tag.c
index 7d38cc0..3aa186d 100644
--- a/tag.c
+++ b/tag.c
@@ -139,6 +139,11 @@ int parse_tag(struct tag *item)
 	return ret;
 }
 
+/*
+ * Look at a signed tag object, and return the offset where
+ * the embedded detached signature begins, or the end of the
+ * data when there is no such signature.
+ */
 size_t parse_signature(const char *buf, unsigned long size)
 {
 	char *eol;
-- 
1.7.7.498.g3f2e50

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v4 2/5] commit: teach --gpg-sign option
  2011-10-20  0:36   ` [PATCH v4 0/5] Signed-commit Junio C Hamano
  2011-10-20  0:36     ` [PATCH v4 1/5] Split GPG interface into its own helper library Junio C Hamano
@ 2011-10-20  0:37     ` Junio C Hamano
  2011-10-20  0:37     ` [PATCH v4 3/5] log: --show-signature Junio C Hamano
                       ` (3 subsequent siblings)
  5 siblings, 0 replies; 35+ messages in thread
From: Junio C Hamano @ 2011-10-20  0:37 UTC (permalink / raw)
  To: git

And this uses the gpg-interface.[ch] to allow signing the commit, i.e.

    $ git commit --gpg-sign -m foo
    You need a passphrase to unlock the secret key for
    user: "Junio C Hamano <gitster@pobox.com>"
    4096-bit RSA key, ID 96AFE6CB, created 2011-10-03 (main key ID 713660A7)

    [master 8457d13] foo
     1 files changed, 1 insertions(+), 0 deletions(-)

The lines of GPG detached signature are placed in new header lines, after
the standard tree/parent/author/committer headers, instead of tucking the
signature block at the end of the commit log message text (similar to how
signed tag is done), for multiple reasons:

 - The signature won't clutter output from "git log" and friends if it is
   in the extra header. If we place it at the end of the log message, we
   would need to teach "git log" and friends to strip the signature block
   with an option.

 - Teaching new versions of "git log" and "gitk" to optionally verify and
   show signatures is cleaner if we structurally know where the signature
   block is (instead of scanning in the commit log message).

 - The signature needs to be stripped upon various commit rewriting
   operations, e.g. rebase, filter-branch, etc. They all already ignore
   unknown headers, but if we place signature in the log message, all of
   these tools (and third-party tools) also need to learn how a signature
   block would look like.

 - When we added the optional encoding header, all the tools (both in tree
   and third-party) that acts on the raw commit object should have been
   fixed to ignore headers they do not understand, so it is not like that
   new header would be more likely to break than extra text in the commit.

A commit made with the above sample sequence would look like this:

    $ git cat-file commit HEAD
    tree 3cd71d90e3db4136e5260ab54599791c4f883b9d
    parent b87755351a47b09cb27d6913e6e0e17e6254a4d4
    author Junio C Hamano <gitster@pobox.com> 1317862251 -0700
    committer Junio C Hamano <gitster@pobox.com> 1317862251 -0700
    gpgsig -----BEGIN PGP SIGNATURE-----
    gpgsig Version: GnuPG v1.4.10 (GNU/Linux)
    gpgsig
    gpgsig iQIcBAABAgAGBQJOjPtrAAoJELC16IaWr+bL4TMP/RSe2Y/jYnCkds9unO5JEnfG
    gpgsig ...
    gpgsig =dt98
    gpgsig -----END PGP SIGNATURE-----

    foo

but "git log" (unless you ask for it with --pretty=raw) output is not
cluttered with the signature information.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/commit-tree.c |   24 +++++++++++++++++++++---
 builtin/commit.c      |   12 ++++++++++--
 builtin/merge.c       |   16 ++++++++++++++--
 commit.c              |   40 +++++++++++++++++++++++++++++++++++++++-
 commit.h              |    2 +-
 notes-cache.c         |    2 +-
 notes-merge.c         |    2 +-
 7 files changed, 87 insertions(+), 11 deletions(-)

diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c
index d083795..a17811f 100644
--- a/builtin/commit-tree.c
+++ b/builtin/commit-tree.c
@@ -8,8 +8,9 @@
 #include "tree.h"
 #include "builtin.h"
 #include "utf8.h"
+#include "gpg-interface.h"
 
-static const char commit_tree_usage[] = "git commit-tree <sha1> [(-p <sha1>)...] < changelog";
+static const char commit_tree_usage[] = "git commit-tree [-S<signer>] <sha1> [(-p <sha1>)...] < changelog";
 
 static void new_parent(struct commit *parent, struct commit_list **parents_p)
 {
@@ -25,6 +26,14 @@ static void new_parent(struct commit *parent, struct commit_list **parents_p)
 	commit_list_insert(parent, parents_p);
 }
 
+static int commit_tree_config(const char *var, const char *value, void *cb)
+{
+	int status = git_gpg_config(var, value, NULL);
+	if (status)
+		return status;
+	return git_default_config(var, value, cb);
+}
+
 int cmd_commit_tree(int argc, const char **argv, const char *prefix)
 {
 	int i;
@@ -32,11 +41,19 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
 	unsigned char tree_sha1[20];
 	unsigned char commit_sha1[20];
 	struct strbuf buffer = STRBUF_INIT;
+	const char *sign_commit = NULL;
 
-	git_config(git_default_config, NULL);
+	git_config(commit_tree_config, NULL);
 
 	if (argc < 2 || !strcmp(argv[1], "-h"))
 		usage(commit_tree_usage);
+
+	if (!memcmp(argv[1], "-S", 2)) {
+		sign_commit = argv[1] + 2;
+		argv++;
+		argc--;
+	}
+
 	if (get_sha1(argv[1], tree_sha1))
 		die("Not a valid object name %s", argv[1]);
 
@@ -56,7 +73,8 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
 	if (strbuf_read(&buffer, 0, 0) < 0)
 		die_errno("git commit-tree: failed to read");
 
-	if (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1, NULL)) {
+	if (commit_tree(buffer.buf, tree_sha1, parents, commit_sha1,
+			NULL, sign_commit)) {
 		strbuf_release(&buffer);
 		return 1;
 	}
diff --git a/builtin/commit.c b/builtin/commit.c
index cbc9613..90cf7e8 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -26,6 +26,7 @@
 #include "unpack-trees.h"
 #include "quote.h"
 #include "submodule.h"
+#include "gpg-interface.h"
 
 static const char * const builtin_commit_usage[] = {
 	"git commit [options] [--] <filepattern>...",
@@ -85,6 +86,8 @@ static int all, edit_flag, also, interactive, patch_interactive, only, amend, si
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 static int no_post_rewrite, allow_empty_message;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
+static char *sign_commit;
+
 /*
  * The default commit message cleanup mode will remove the lines
  * beginning with # (shell comments) and leading and trailing
@@ -144,6 +147,8 @@ static struct option builtin_commit_options[] = {
 	OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
 	OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
 	OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"),
+	{ OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id",
+	  "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
 	/* end commit message options */
 
 	OPT_GROUP("Commit contents options"),
@@ -1323,6 +1328,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1,
 static int git_commit_config(const char *k, const char *v, void *cb)
 {
 	struct wt_status *s = cb;
+	int status;
 
 	if (!strcmp(k, "commit.template"))
 		return git_config_pathname(&template_file, k, v);
@@ -1330,7 +1336,9 @@ static int git_commit_config(const char *k, const char *v, void *cb)
 		include_status = git_config_bool(k, v);
 		return 0;
 	}
-
+	status = git_gpg_config(k, v, NULL);
+	if (status)
+		return status;
 	return git_status_config(k, v, s);
 }
 
@@ -1481,7 +1489,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 	}
 
 	if (commit_tree(sb.buf, active_cache_tree->sha1, parents, sha1,
-			author_ident.buf)) {
+			author_ident.buf, sign_commit)) {
 		rollback_index_files();
 		die(_("failed to write commit object"));
 	}
diff --git a/builtin/merge.c b/builtin/merge.c
index ab4077f..53cff02 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -26,6 +26,7 @@
 #include "merge-recursive.h"
 #include "resolve-undo.h"
 #include "remote.h"
+#include "gpg-interface.h"
 
 #define DEFAULT_TWOHEAD (1<<0)
 #define DEFAULT_OCTOPUS (1<<1)
@@ -63,6 +64,7 @@ static int allow_rerere_auto;
 static int abort_current_merge;
 static int show_progress = -1;
 static int default_to_upstream;
+static const char *sign_commit;
 
 static struct strategy all_strategy[] = {
 	{ "recursive",  DEFAULT_TWOHEAD | NO_TRIVIAL },
@@ -206,6 +208,8 @@ static struct option builtin_merge_options[] = {
 	OPT_BOOLEAN(0, "abort", &abort_current_merge,
 		"abort the current in-progress merge"),
 	OPT_SET_INT(0, "progress", &show_progress, "force progress reporting", 1),
+	{ OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id",
+	  "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
 	OPT_END()
 };
 
@@ -525,6 +529,8 @@ static void parse_branch_merge_options(char *bmo)
 
 static int git_merge_config(const char *k, const char *v, void *cb)
 {
+	int status;
+
 	if (branch && !prefixcmp(k, "branch.") &&
 		!prefixcmp(k + 7, branch) &&
 		!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
@@ -562,6 +568,10 @@ static int git_merge_config(const char *k, const char *v, void *cb)
 		default_to_upstream = git_config_bool(k, v);
 		return 0;
 	}
+
+	status = git_gpg_config(k, v, NULL);
+	if (status)
+		return status;
 	return git_diff_ui_config(k, v, cb);
 }
 
@@ -870,7 +880,8 @@ static int merge_trivial(void)
 	parent->next->item = remoteheads->item;
 	parent->next->next = NULL;
 	run_prepare_commit_msg();
-	commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
+	commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL,
+		    sign_commit);
 	finish(result_commit, "In-index merge");
 	drop_save();
 	return 0;
@@ -900,7 +911,8 @@ static int finish_automerge(struct commit_list *common,
 	free_commit_list(remoteheads);
 	strbuf_addch(&merge_msg, '\n');
 	run_prepare_commit_msg();
-	commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
+	commit_tree(merge_msg.buf, result_tree, parents, result_commit,
+		    NULL, sign_commit);
 	strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
 	finish(result_commit, buf.buf);
 	strbuf_release(&buf);
diff --git a/commit.c b/commit.c
index 97b4327..4bff3cd 100644
--- a/commit.c
+++ b/commit.c
@@ -6,6 +6,7 @@
 #include "diff.h"
 #include "revision.h"
 #include "notes.h"
+#include "gpg-interface.h"
 
 int save_commit_buffer = 1;
 
@@ -814,6 +815,40 @@ struct commit_list *reduce_heads(struct commit_list *heads)
 	return result;
 }
 
+static const char gpg_sig_header[] = "gpgsig ";
+static const int gpg_sig_header_len = sizeof(gpg_sig_header) - 1;
+
+static int do_sign_commit(struct strbuf *buf, const char *keyid)
+{
+	struct strbuf sig = STRBUF_INIT;
+	int inspos, copypos;
+
+	/* find the end of the header */
+	inspos = strstr(buf->buf, "\n\n") - buf->buf + 1;
+
+	if (!keyid || !*keyid)
+		keyid = get_signing_key();
+	if (sign_buffer(buf, &sig, keyid)) {
+		strbuf_release(&sig);
+		return -1;
+	}
+
+	for (copypos = 0; sig.buf[copypos]; ) {
+		const char *bol = sig.buf + copypos;
+		const char *eol = strchrnul(bol, '\n');
+		int len = (eol - bol) + !!*eol;
+
+		strbuf_insert(buf, inspos, gpg_sig_header, gpg_sig_header_len);
+		inspos += gpg_sig_header_len;
+		strbuf_insert(buf, inspos, bol, len);
+		inspos += len;
+		copypos += len;
+	}
+	strbuf_release(&sig);
+	return 0;
+}
+
+
 static const char commit_utf8_warn[] =
 "Warning: commit message does not conform to UTF-8.\n"
 "You may want to amend it after fixing the message, or set the config\n"
@@ -821,7 +856,7 @@ static const char commit_utf8_warn[] =
 
 int commit_tree(const char *msg, unsigned char *tree,
 		struct commit_list *parents, unsigned char *ret,
-		const char *author)
+		const char *author, const char *sign_commit)
 {
 	int result;
 	int encoding_is_utf8;
@@ -864,6 +899,9 @@ int commit_tree(const char *msg, unsigned char *tree,
 	if (encoding_is_utf8 && !is_utf8(buffer.buf))
 		fprintf(stderr, commit_utf8_warn);
 
+	if (sign_commit && do_sign_commit(&buffer, sign_commit))
+		return -1;
+
 	result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
 	strbuf_release(&buffer);
 	return result;
diff --git a/commit.h b/commit.h
index 12d100b8..8c2419b 100644
--- a/commit.h
+++ b/commit.h
@@ -175,6 +175,6 @@ struct commit_list *reduce_heads(struct commit_list *heads);
 
 extern int commit_tree(const char *msg, unsigned char *tree,
 		struct commit_list *parents, unsigned char *ret,
-		const char *author);
+		       const char *author, const char *sign_commit);
 
 #endif /* COMMIT_H */
diff --git a/notes-cache.c b/notes-cache.c
index 4c8984e..c36a960 100644
--- a/notes-cache.c
+++ b/notes-cache.c
@@ -56,7 +56,7 @@ int notes_cache_write(struct notes_cache *c)
 
 	if (write_notes_tree(&c->tree, tree_sha1))
 		return -1;
-	if (commit_tree(c->validity, tree_sha1, NULL, commit_sha1, NULL) < 0)
+	if (commit_tree(c->validity, tree_sha1, NULL, commit_sha1, NULL, NULL) < 0)
 		return -1;
 	if (update_ref("update notes cache", c->tree.ref, commit_sha1, NULL,
 		       0, QUIET_ON_ERR) < 0)
diff --git a/notes-merge.c b/notes-merge.c
index e1aaf43..c29c434 100644
--- a/notes-merge.c
+++ b/notes-merge.c
@@ -546,7 +546,7 @@ void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
 		/* else: t->ref points to nothing, assume root/orphan commit */
 	}
 
-	if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL))
+	if (commit_tree(msg, tree_sha1, parents, result_sha1, NULL, NULL))
 		die("Failed to commit notes tree to database");
 }
 
-- 
1.7.7.498.g3f2e50

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v4 3/5] log: --show-signature
  2011-10-20  0:36   ` [PATCH v4 0/5] Signed-commit Junio C Hamano
  2011-10-20  0:36     ` [PATCH v4 1/5] Split GPG interface into its own helper library Junio C Hamano
  2011-10-20  0:37     ` [PATCH v4 2/5] commit: teach --gpg-sign option Junio C Hamano
@ 2011-10-20  0:37     ` Junio C Hamano
  2011-10-20  0:37     ` [PATCH v4 4/5] t7004: extract generic "GPG testing" bits Junio C Hamano
                       ` (2 subsequent siblings)
  5 siblings, 0 replies; 35+ messages in thread
From: Junio C Hamano @ 2011-10-20  0:37 UTC (permalink / raw)
  To: git

This teaches the "log" family of commands to pass the GPG signature in the
commit objects to "gpg --verify" via the verify_signed_buffer() interface
used to verify signed tag objects. E.g.

    $ git show --show-signature -s HEAD

shows GPG output in the header part of the output.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 commit.c   |   34 ++++++++++++++++++++++++++++++++++
 commit.h   |    3 +++
 log-tree.c |   39 +++++++++++++++++++++++++++++++++++++++
 revision.c |    2 ++
 revision.h |    1 +
 5 files changed, 79 insertions(+), 0 deletions(-)

diff --git a/commit.c b/commit.c
index 4bff3cd..93045a2 100644
--- a/commit.c
+++ b/commit.c
@@ -848,6 +848,40 @@ static int do_sign_commit(struct strbuf *buf, const char *keyid)
 	return 0;
 }
 
+int parse_signed_commit(const unsigned char *sha1,
+			struct strbuf *payload, struct strbuf *signature)
+{
+	unsigned long size;
+	enum object_type type;
+	char *buffer = read_sha1_file(sha1, &type, &size);
+	int in_header, saw_signature = -1;
+	char *line;
+
+	if (!buffer || type != OBJ_COMMIT)
+		goto cleanup;
+
+	line = buffer;
+	in_header = 1;
+	saw_signature = 0;
+	while (*line) {
+		char *next = strchrnul(line, '\n');
+		if (*next)
+			next++;
+		if (in_header && !prefixcmp(line, gpg_sig_header)) {
+			const char *sig = line + gpg_sig_header_len;
+			strbuf_add(signature, sig, next - sig);
+			saw_signature = 1;
+		} else {
+			strbuf_add(payload, line, next - line);
+		}
+		if (*line == '\n')
+			in_header = 0;
+		line = next;
+	}
+ cleanup:
+	free(buffer);
+	return saw_signature;
+}
 
 static const char commit_utf8_warn[] =
 "Warning: commit message does not conform to UTF-8.\n"
diff --git a/commit.h b/commit.h
index 8c2419b..1885471 100644
--- a/commit.h
+++ b/commit.h
@@ -177,4 +177,7 @@ extern int commit_tree(const char *msg, unsigned char *tree,
 		struct commit_list *parents, unsigned char *ret,
 		       const char *author, const char *sign_commit);
 
+extern int parse_signed_commit(const unsigned char *sha1,
+			       struct strbuf *message, struct strbuf *signature);
+
 #endif /* COMMIT_H */
diff --git a/log-tree.c b/log-tree.c
index 24c295e..f7b6976 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -8,6 +8,7 @@
 #include "refs.h"
 #include "string-list.h"
 #include "color.h"
+#include "gpg-interface.h"
 
 struct decoration name_decoration = { "object names" };
 
@@ -395,6 +396,41 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit,
 	*extra_headers_p = extra_headers;
 }
 
+static void show_signature(struct rev_info *opt, struct commit *commit)
+{
+	struct strbuf payload = STRBUF_INIT;
+	struct strbuf signature = STRBUF_INIT;
+	struct strbuf gpg_output = STRBUF_INIT;
+	int status;
+	const char *color, *reset, *bol, *eol;
+
+	if (parse_signed_commit(commit->object.sha1, &payload, &signature) <= 0)
+		goto out;
+
+	status = verify_signed_buffer(payload.buf, payload.len,
+				      signature.buf, signature.len,
+				      &gpg_output);
+	if (status && !gpg_output.len)
+		strbuf_addstr(&gpg_output, "No signature\n");
+
+	color = diff_get_color_opt(&opt->diffopt,
+				   status ? DIFF_WHITESPACE : DIFF_FRAGINFO);
+	reset = diff_get_color_opt(&opt->diffopt, DIFF_RESET);
+
+	bol = gpg_output.buf;
+	while (*bol) {
+		eol = strchrnul(bol, '\n');
+		printf("%s%.*s%s%s", color, (int)(eol - bol), bol, reset,
+		       *eol ? "\n" : "");
+		bol = (*eol) ? (eol + 1) : eol;
+	}
+
+ out:
+	strbuf_release(&gpg_output);
+	strbuf_release(&payload);
+	strbuf_release(&signature);
+}
+
 void show_log(struct rev_info *opt)
 {
 	struct strbuf msgbuf = STRBUF_INIT;
@@ -502,6 +538,9 @@ void show_log(struct rev_info *opt)
 		}
 	}
 
+	if (opt->show_signature)
+		show_signature(opt, commit);
+
 	if (!commit->buffer)
 		return;
 
diff --git a/revision.c b/revision.c
index c46cfaa..860a312 100644
--- a/revision.c
+++ b/revision.c
@@ -1381,6 +1381,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
 		revs->show_notes = 1;
 		revs->show_notes_given = 1;
 		revs->notes_opt.use_default_notes = 1;
+	} else if (!strcmp(arg, "--show-signature")) {
+		revs->show_signature = 1;
 	} else if (!prefixcmp(arg, "--show-notes=") ||
 		   !prefixcmp(arg, "--notes=")) {
 		struct strbuf buf = STRBUF_INIT;
diff --git a/revision.h b/revision.h
index 3d64ada..198bb95 100644
--- a/revision.h
+++ b/revision.h
@@ -89,6 +89,7 @@ struct rev_info {
 			show_merge:1,
 			show_notes:1,
 			show_notes_given:1,
+			show_signature:1,
 			pretty_given:1,
 			abbrev_commit:1,
 			abbrev_commit_given:1,
-- 
1.7.7.498.g3f2e50

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v4 4/5] t7004: extract generic "GPG testing" bits
  2011-10-20  0:36   ` [PATCH v4 0/5] Signed-commit Junio C Hamano
                       ` (2 preceding siblings ...)
  2011-10-20  0:37     ` [PATCH v4 3/5] log: --show-signature Junio C Hamano
@ 2011-10-20  0:37     ` Junio C Hamano
  2011-10-20  0:37     ` [PATCH v4 5/5] test "commit -S" and "log --show-signature" Junio C Hamano
  2011-10-22  5:01     ` [PATCH 7/5] pretty: %G[?GS] placeholders Junio C Hamano
  5 siblings, 0 replies; 35+ messages in thread
From: Junio C Hamano @ 2011-10-20  0:37 UTC (permalink / raw)
  To: git

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 t/lib-gpg.sh          |   29 +++++++++++++++++++++++++++++
 t/lib-gpg/pubring.gpg |  Bin 0 -> 1164 bytes
 t/lib-gpg/random_seed |  Bin 0 -> 600 bytes
 t/lib-gpg/secring.gpg |  Bin 0 -> 1237 bytes
 t/lib-gpg/trustdb.gpg |  Bin 0 -> 1280 bytes
 t/t7004-tag.sh        |   29 +----------------------------
 t/t7004/pubring.gpg   |  Bin 1164 -> 0 bytes
 t/t7004/random_seed   |  Bin 600 -> 0 bytes
 t/t7004/secring.gpg   |  Bin 1237 -> 0 bytes
 t/t7004/trustdb.gpg   |  Bin 1280 -> 0 bytes
 10 files changed, 30 insertions(+), 28 deletions(-)
 create mode 100644 t/lib-gpg.sh
 create mode 100644 t/lib-gpg/pubring.gpg
 create mode 100644 t/lib-gpg/random_seed
 create mode 100644 t/lib-gpg/secring.gpg
 create mode 100644 t/lib-gpg/trustdb.gpg
 delete mode 100644 t/t7004/pubring.gpg
 delete mode 100644 t/t7004/random_seed
 delete mode 100644 t/t7004/secring.gpg
 delete mode 100644 t/t7004/trustdb.gpg

diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh
new file mode 100644
index 0000000..eb09027
--- /dev/null
+++ b/t/lib-gpg.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+# Check if gpg is available
+gpg --version >/dev/null 2>/dev/null
+if [ $? -eq 127 ]; then
+	say "# gpg not found - skipping tag signing and verification tests"
+else
+	# As said here: http://www.gnupg.org/documentation/faqs.html#q6.19
+	# the gpg version 1.0.6 didn't parse trust packets correctly, so for
+	# that version, creation of signed tags using the generated key fails.
+	case "$(gpg --version)" in
+	'gpg (GnuPG) 1.0.6'*)
+		say "Skipping signed tag tests, because a bug in 1.0.6 version"
+		;;
+	*)
+		test_set_prereq GPG
+		;;
+	esac
+fi
+
+# key generation info: gpg --homedir t/t7004 --gen-key
+# Type DSA and Elgamal, size 2048 bits, no expiration date.
+# Name and email: C O Mitter <committer@example.com>
+# No password given, to enable non-interactive operation.
+
+cp -R "$TEST_DIRECTORY"/lib-gpg ./gpghome
+chmod 0700 gpghome
+GNUPGHOME="$(pwd)/gpghome"
+export GNUPGHOME
diff --git a/t/lib-gpg/pubring.gpg b/t/lib-gpg/pubring.gpg
new file mode 100644
index 0000000000000000000000000000000000000000..83855fa4e1c6c37afe550c17afa1e7971042ded5
GIT binary patch
literal 1164
zcmV;71ateD0ipy(XUi!O1OT%L_{gO;4KKDfwF;#WWYZ2?*>Ph_j9y>uobJf#jXEbw
zQUOEXs+@;X*)1kyN*7VwW}f-GD3>Vlu2F6j{T^Kt%(kMo(W3C}W^X)NL|0VdGIzc`
z5@TY!;Du~AFGW=#0vYgS@U2j8Q{Qe9F~lQEBVt+(yjM1jQ{YLe0lxsC<HX~BiFSU3
zs3oq~QtxsAzbwuV1N{{&PcudMk6}p><?O}^6vikhMm+>LD}W25r?wdKr$(|R8_;Mn
z&Yh%2Hbu9IX>#(R8S&@jVI!K^K7AI$=A+*-qJ>-J^fdR_m5GZao*%P{nee=O$Eb$s
zes#O%4Z_4OAZW}8Ey8%%w->rA>z!nf)&NPikkr&pkRlB>=W+x68fwevWl9Ys2R3m2
zNPf<ig>x1y^AXyemu>Q}DAp%==Q`5sCrBzLaH^rOm1NLtt-ig*IfY?HMdv66@%u%#
zy0K62;Z+4O3Ol6i*<7GlXeW6FC?0yV!c_#yGozNjrOE#8+Rw@Z8cGAG=eE%uMUW=V
zX(76db;AG@lD)hAv?4<wPasWcbaZ8MAUtDlZEb0EbY*ftWq4t2aBO8RV{dIfh+YH}
z5dr`n1p-EA%P9gI0|pBT2nPcK0~G=T0~P`S0v-VZ7k~f?2@n&u^&ZXMLk)Bt0G~<e
ziCMX)QRqT4mEiivJ-X>{Mxg+pzucC_!d-8(MV+3G?_=VDC^nQ}umS)BxdIIYMrX@T
z5C{Oyxp_ZR>*yfS@@M%BVY6{X4EXUYpbiBs(uw7knRB|b)>)=o+wQ%-v34o&a|fQ;
z&R=xVK8Z-jS8R4m_gj2C$xPq$xv1H553!y9ip8A~vxoyq&fFbw!L$zU@hDh#f$r>>
zAYPcTf%K%xv5<+JivV@Da!&`-v8eIo5ApKIdiK7E`krA1X!I73&)t#yo}GERUSnBN
zlK-+<I<**0!u}jvbPz5b{hhe8_PVcR!i8ObFp-W0bqNWKA+W14zV9Mj$a~(KZ-0uf
z|BVvaf!K}k92MNalP4hQ^Yxqk-*SzY6q{6Cw*+Fr_+LZp=BFzuo3pVX*T?Ic-|hbZ
z1PurPv|=$EKX~Bz_Bz;JW71^>8;AnsY?5ub{hajO)@PHTt`On@Z;}jyeue<@oS$3l
zEu9m5O={<O&85HqUN|R$bZiPXX4YsWsPOn@@M)Lf;hyI_(eM$*#Sf3}T=1jzGvsiy
ziKuCmV^)`UC@8ma=jBiHqr!1L4>w`5P=-vvIiJTGj0-lRpc+EU9(`B#rn1m1WqmXr
z5Z+N|DR*y*k<4fY0I_eP7qVo9Fx58WMwaK^N~I=>1VN29R|Umg^2q$WGlE#9AFC2_
zOYG5M$}OL?FQ)ej0!KGwY-hAFz#wAPr>3&{`s7!HUZ22Y7KY{9ly1wQ8^hqBojPv8
zh)Dz(5dr`S1p-EA%T59t3;+rV5EHib9?jlE4Z-XHpqd%~B34SUmtRi+{;vv2ei?p%
eLjau^#pu5ba=n|GS94<zkje^zT1YFf0ssTLm?$>@

literal 0
HcmV?d00001

diff --git a/t/lib-gpg/random_seed b/t/lib-gpg/random_seed
new file mode 100644
index 0000000000000000000000000000000000000000..8fed1339ed0a744e5663f4a5e6b6ac9bae3d8524
GIT binary patch
literal 600
zcmV-e0;m1=h9nBV>1C6QsKJEiEJaD@Q3F8s5u<$E+<2(By)JAZSxviTsXg(wKC+O%
zzvV{Z>W3*k?r7~pgmmkbw8-x{Am!eeN)z?cwIHcT2jqgiA(SXo<iO=E?cY80`p#w8
z)O-&?SnwsJ=1VJ-?26&*g88Nr8E=g2onRW^(c+2nJlX)?dmK)tPO0EY-!B!vMCv1)
z-AOW(3WuF+7IdSxMnzrDgnMqVU=|+YFxlY|VeR+Fg<%C@0Xupi0<S7QYJyFTR$}FQ
zzoSAbU>CoCKWKX;!3@L_U=aFUm!M<>ILG}$`bfnadAkLQbI-upV7Qwf^OE&N45Pz<
zk~^KlzNC6)d@QGv=K5-At&A8FS&MQSR`LB}@R1?A3K1p(vM>7CK}EfFhmBJd&cH^-
z(3Ih^`VuoVBB|w~p!Q^#DY%V2A2FhXu<Bp*L)lSCUdqRyI5wxMG&E1sL$)E$Zo&pJ
zgy#;fENqHImgN>LL2!7DhfZ}&;BSAyz=T0#S?2+NET5St@16L?YI?5Io%<uD|2}hl
zx0xsuefz1+bM^-ZIgtKs=)&VAI8(MfytvM>t>%~nsXUb~*EkptHiN?W{=DRu_s;2u
ziHh{2&>;CQO7;>{$DN33_Ef}g+;b<2hIF^p(Y>^riLBb*Y2Xw>F8)jp49&oLKJOic
z+V{Lt!_`eKGhyk5Edie{-^#n!TFlsfux*QBRZEh^4SVePPmb{BvF|>sKd2cYg@vKp
mVI8jcB1(k(tlt^Kr<{EMs>|b*d70nyVMQcc%xEnE(#Uq3d^-35

literal 0
HcmV?d00001

diff --git a/t/lib-gpg/secring.gpg b/t/lib-gpg/secring.gpg
new file mode 100644
index 0000000000000000000000000000000000000000..d831cd9eb3eee613d3c0e1a71093ae01ea7347e3
GIT binary patch
literal 1237
zcmV;`1S<QL0lNf7XUi!O1OT%L_{gO;4KKDfwF;#WWYZ2?*>Ph_j9y>uobJf#jXEbw
zQUOEXs+@;X*)1kyN*7VwW}f-GD3>Vlu2F6j{T^Kt%(kMo(W3C}W^X)NL|0VdGIzc`
z5@TY!;Du~AFGW=#0vYgS@U2j8Q{Qe9F~lQEBVt+(yjM1jQ{YLe0lxsC<HX~BiFSU3
zs3oq~QtxsAzbwuV1N{{&PcudMk6}p><?O}^6vikhMm+>LD}W25r?wdKr$(|R8_;Mn
z&Yh%2Hbu9IX>#(R8S&@jVI!K^K7AI$=A+*-qJ>-J^fdR_m5GZao*%P{nee=O$Eb$s
zes#O%4Z_4OAZW}8Ey8%%w->rA>z!nf)&NPikkr&pkRlB>=W+x68fwevWl9Ys2R3m2
zNPf<ig>x1y^AXyemu>Q}DAp%==Q`5sCrBzLaH^rOm1NLtt-ig*IfY?HMdv66@%u%#
zy0K62;Z+4O3Ol6i*<7GlXeW6FC?0yV!c_#yGozNjrOE#8+Rw@Z8cGAG=eE%uMUW=V
zX(76db;AG@lD)hA005tK#LT31ryNoF9o-(`X`Xl5w88@mle8j3AWtAoX>@dDav(fo
zZ*6U9baZ8MKxKGgZE$R5E@N+PK8Rif6A=Oc9t8qMXUi!98v_Ol2?z%R0s|ES0|OQU
z0RkQY0vCV)3JDMsw)Gy(-a`#^9RQ%c+5fOQwB&AM#DD9)s1L@!pm9S0pF>@|C2*qL
z%1j5#NZ6^U*PB>^d9VTi0G$Fo1V(4eP7nwH&bfI%Q|ss;(eh{c3}LfzMGW}yE1(Vq
zEz*hQmYH+9vesFqTifovzOi;G@N);A+0I{d(LRYt$5(82OZQuRJjqPo^tq_na}Tkd
z|BA((5wnN`O3vIJaly0>?(ryCcY*Hgm>^!5uz~cX%CV4%oQnW;wsKDg)3K=W<qz@l
z$a?m^i29yk259sakI&ta{GOe8x?W>hQIh|%Svs{CO~U>hTXYaE9sQlSv-Y~LWWt4A
ze=w1b1$7Atj3KbAF~092T*!Ogn{R)Lum6n_*@4)N@EjG~z>_B+>GSoQ{oitpmlT^+
zUAF{c!T4W8?B=H{DVwvgAlJw1n&0jJ00a#P0JLH;8$Wp9`Sv>4USrZ_1sjM0<!q8|
zxc!{;-PUK5pso<&0dJBFgMNkp@|>Sr>@A%Wd`)WSc+I820A4sJf^=*OHfGjnC8+TD
zW$<a2;o+X=JkjtG#>Eeh?p*Mr_A}&gvx%r_lw($xcPJ>gap&bv^P|FXJr6fwvQUOh
zz&W4C8jK4zqM#Z=%N~7K_NKDXEM<K(9uVG9XDN4YijmA{1^}^dq8GAcg)r4N<3^U}
z-b$q=iUdK8Hdh73UGm8MyfcDWr5~#ja!c&dVahF^v@fRj3IazrWNc@&F~A^V)u*Pi
z`ugNogkGP(V-|+x+mvq0pc}*Bpq)By!2khN4M6SOU21NKpXW5P=UU_LoDzvrTdR;4
zU)W&&o9V6>MQOk{A%?1(Ozs{Qqlifa7!d*h2?YX1XUk3k8w>yn2@n&u^&ZXMLk+>~
z0HDxvfGrJTMY@$ys#k#t?D63Zq=W#R3eUeT(L61`VGHe?zP;`rnta#-umS)8nA%Rq

literal 0
HcmV?d00001

diff --git a/t/lib-gpg/trustdb.gpg b/t/lib-gpg/trustdb.gpg
new file mode 100644
index 0000000000000000000000000000000000000000..abace962b8bf84be688a6f27e4ebd0ee7052f210
GIT binary patch
literal 1280
zcmZQfFGy!*W@Ke#U|?`dKkWykumMIcY@%4iM%7^n6rj+M4;MLzzlOX&pwTnxkD-}P
zc^HbXN0fL!SIq1?>env3?W^3`d(OOU5YNaX{KU(k^<0;M@87ONv)_6ZxD={-=<kYO
M2Ud3=2BC}r0AuhNr2qf`

literal 0
HcmV?d00001

diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index 097ce2b..ded5c86 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -8,6 +8,7 @@ test_description='git tag
 Tests for operations with tags.'
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY/lib-gpg.sh"
 
 # creating and listing lightweight tags:
 
@@ -585,24 +586,6 @@ test_expect_success \
 	test_cmp expect actual
 '
 
-# subsequent tests require gpg; check if it is available
-gpg --version >/dev/null 2>/dev/null
-if [ $? -eq 127 ]; then
-	say "# gpg not found - skipping tag signing and verification tests"
-else
-	# As said here: http://www.gnupg.org/documentation/faqs.html#q6.19
-	# the gpg version 1.0.6 didn't parse trust packets correctly, so for
-	# that version, creation of signed tags using the generated key fails.
-	case "$(gpg --version)" in
-	'gpg (GnuPG) 1.0.6'*)
-		say "Skipping signed tag tests, because a bug in 1.0.6 version"
-		;;
-	*)
-		test_set_prereq GPG
-		;;
-	esac
-fi
-
 # trying to verify annotated non-signed tags:
 
 test_expect_success GPG \
@@ -625,16 +608,6 @@ test_expect_success GPG \
 
 # creating and verifying signed tags:
 
-# key generation info: gpg --homedir t/t7004 --gen-key
-# Type DSA and Elgamal, size 2048 bits, no expiration date.
-# Name and email: C O Mitter <committer@example.com>
-# No password given, to enable non-interactive operation.
-
-cp -R "$TEST_DIRECTORY"/t7004 ./gpghome
-chmod 0700 gpghome
-GNUPGHOME="$(pwd)/gpghome"
-export GNUPGHOME
-
 get_tag_header signed-tag $commit commit $time >expect
 echo 'A signed tag message' >>expect
 echo '-----BEGIN PGP SIGNATURE-----' >>expect
diff --git a/t/t7004/pubring.gpg b/t/t7004/pubring.gpg
deleted file mode 100644
index 83855fa4e1c6c37afe550c17afa1e7971042ded5..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 1164
zcmV;71ateD0ipy(XUi!O1OT%L_{gO;4KKDfwF;#WWYZ2?*>Ph_j9y>uobJf#jXEbw
zQUOEXs+@;X*)1kyN*7VwW}f-GD3>Vlu2F6j{T^Kt%(kMo(W3C}W^X)NL|0VdGIzc`
z5@TY!;Du~AFGW=#0vYgS@U2j8Q{Qe9F~lQEBVt+(yjM1jQ{YLe0lxsC<HX~BiFSU3
zs3oq~QtxsAzbwuV1N{{&PcudMk6}p><?O}^6vikhMm+>LD}W25r?wdKr$(|R8_;Mn
z&Yh%2Hbu9IX>#(R8S&@jVI!K^K7AI$=A+*-qJ>-J^fdR_m5GZao*%P{nee=O$Eb$s
zes#O%4Z_4OAZW}8Ey8%%w->rA>z!nf)&NPikkr&pkRlB>=W+x68fwevWl9Ys2R3m2
zNPf<ig>x1y^AXyemu>Q}DAp%==Q`5sCrBzLaH^rOm1NLtt-ig*IfY?HMdv66@%u%#
zy0K62;Z+4O3Ol6i*<7GlXeW6FC?0yV!c_#yGozNjrOE#8+Rw@Z8cGAG=eE%uMUW=V
zX(76db;AG@lD)hAv?4<wPasWcbaZ8MAUtDlZEb0EbY*ftWq4t2aBO8RV{dIfh+YH}
z5dr`n1p-EA%P9gI0|pBT2nPcK0~G=T0~P`S0v-VZ7k~f?2@n&u^&ZXMLk)Bt0G~<e
ziCMX)QRqT4mEiivJ-X>{Mxg+pzucC_!d-8(MV+3G?_=VDC^nQ}umS)BxdIIYMrX@T
z5C{Oyxp_ZR>*yfS@@M%BVY6{X4EXUYpbiBs(uw7knRB|b)>)=o+wQ%-v34o&a|fQ;
z&R=xVK8Z-jS8R4m_gj2C$xPq$xv1H553!y9ip8A~vxoyq&fFbw!L$zU@hDh#f$r>>
zAYPcTf%K%xv5<+JivV@Da!&`-v8eIo5ApKIdiK7E`krA1X!I73&)t#yo}GERUSnBN
zlK-+<I<**0!u}jvbPz5b{hhe8_PVcR!i8ObFp-W0bqNWKA+W14zV9Mj$a~(KZ-0uf
z|BVvaf!K}k92MNalP4hQ^Yxqk-*SzY6q{6Cw*+Fr_+LZp=BFzuo3pVX*T?Ic-|hbZ
z1PurPv|=$EKX~Bz_Bz;JW71^>8;AnsY?5ub{hajO)@PHTt`On@Z;}jyeue<@oS$3l
zEu9m5O={<O&85HqUN|R$bZiPXX4YsWsPOn@@M)Lf;hyI_(eM$*#Sf3}T=1jzGvsiy
ziKuCmV^)`UC@8ma=jBiHqr!1L4>w`5P=-vvIiJTGj0-lRpc+EU9(`B#rn1m1WqmXr
z5Z+N|DR*y*k<4fY0I_eP7qVo9Fx58WMwaK^N~I=>1VN29R|Umg^2q$WGlE#9AFC2_
zOYG5M$}OL?FQ)ej0!KGwY-hAFz#wAPr>3&{`s7!HUZ22Y7KY{9ly1wQ8^hqBojPv8
zh)Dz(5dr`S1p-EA%T59t3;+rV5EHib9?jlE4Z-XHpqd%~B34SUmtRi+{;vv2ei?p%
eLjau^#pu5ba=n|GS94<zkje^zT1YFf0ssTLm?$>@

diff --git a/t/t7004/random_seed b/t/t7004/random_seed
deleted file mode 100644
index 8fed1339ed0a744e5663f4a5e6b6ac9bae3d8524..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 600
zcmV-e0;m1=h9nBV>1C6QsKJEiEJaD@Q3F8s5u<$E+<2(By)JAZSxviTsXg(wKC+O%
zzvV{Z>W3*k?r7~pgmmkbw8-x{Am!eeN)z?cwIHcT2jqgiA(SXo<iO=E?cY80`p#w8
z)O-&?SnwsJ=1VJ-?26&*g88Nr8E=g2onRW^(c+2nJlX)?dmK)tPO0EY-!B!vMCv1)
z-AOW(3WuF+7IdSxMnzrDgnMqVU=|+YFxlY|VeR+Fg<%C@0Xupi0<S7QYJyFTR$}FQ
zzoSAbU>CoCKWKX;!3@L_U=aFUm!M<>ILG}$`bfnadAkLQbI-upV7Qwf^OE&N45Pz<
zk~^KlzNC6)d@QGv=K5-At&A8FS&MQSR`LB}@R1?A3K1p(vM>7CK}EfFhmBJd&cH^-
z(3Ih^`VuoVBB|w~p!Q^#DY%V2A2FhXu<Bp*L)lSCUdqRyI5wxMG&E1sL$)E$Zo&pJ
zgy#;fENqHImgN>LL2!7DhfZ}&;BSAyz=T0#S?2+NET5St@16L?YI?5Io%<uD|2}hl
zx0xsuefz1+bM^-ZIgtKs=)&VAI8(MfytvM>t>%~nsXUb~*EkptHiN?W{=DRu_s;2u
ziHh{2&>;CQO7;>{$DN33_Ef}g+;b<2hIF^p(Y>^riLBb*Y2Xw>F8)jp49&oLKJOic
z+V{Lt!_`eKGhyk5Edie{-^#n!TFlsfux*QBRZEh^4SVePPmb{BvF|>sKd2cYg@vKp
mVI8jcB1(k(tlt^Kr<{EMs>|b*d70nyVMQcc%xEnE(#Uq3d^-35

diff --git a/t/t7004/secring.gpg b/t/t7004/secring.gpg
deleted file mode 100644
index d831cd9eb3eee613d3c0e1a71093ae01ea7347e3..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 1237
zcmV;`1S<QL0lNf7XUi!O1OT%L_{gO;4KKDfwF;#WWYZ2?*>Ph_j9y>uobJf#jXEbw
zQUOEXs+@;X*)1kyN*7VwW}f-GD3>Vlu2F6j{T^Kt%(kMo(W3C}W^X)NL|0VdGIzc`
z5@TY!;Du~AFGW=#0vYgS@U2j8Q{Qe9F~lQEBVt+(yjM1jQ{YLe0lxsC<HX~BiFSU3
zs3oq~QtxsAzbwuV1N{{&PcudMk6}p><?O}^6vikhMm+>LD}W25r?wdKr$(|R8_;Mn
z&Yh%2Hbu9IX>#(R8S&@jVI!K^K7AI$=A+*-qJ>-J^fdR_m5GZao*%P{nee=O$Eb$s
zes#O%4Z_4OAZW}8Ey8%%w->rA>z!nf)&NPikkr&pkRlB>=W+x68fwevWl9Ys2R3m2
zNPf<ig>x1y^AXyemu>Q}DAp%==Q`5sCrBzLaH^rOm1NLtt-ig*IfY?HMdv66@%u%#
zy0K62;Z+4O3Ol6i*<7GlXeW6FC?0yV!c_#yGozNjrOE#8+Rw@Z8cGAG=eE%uMUW=V
zX(76db;AG@lD)hA005tK#LT31ryNoF9o-(`X`Xl5w88@mle8j3AWtAoX>@dDav(fo
zZ*6U9baZ8MKxKGgZE$R5E@N+PK8Rif6A=Oc9t8qMXUi!98v_Ol2?z%R0s|ES0|OQU
z0RkQY0vCV)3JDMsw)Gy(-a`#^9RQ%c+5fOQwB&AM#DD9)s1L@!pm9S0pF>@|C2*qL
z%1j5#NZ6^U*PB>^d9VTi0G$Fo1V(4eP7nwH&bfI%Q|ss;(eh{c3}LfzMGW}yE1(Vq
zEz*hQmYH+9vesFqTifovzOi;G@N);A+0I{d(LRYt$5(82OZQuRJjqPo^tq_na}Tkd
z|BA((5wnN`O3vIJaly0>?(ryCcY*Hgm>^!5uz~cX%CV4%oQnW;wsKDg)3K=W<qz@l
z$a?m^i29yk259sakI&ta{GOe8x?W>hQIh|%Svs{CO~U>hTXYaE9sQlSv-Y~LWWt4A
ze=w1b1$7Atj3KbAF~092T*!Ogn{R)Lum6n_*@4)N@EjG~z>_B+>GSoQ{oitpmlT^+
zUAF{c!T4W8?B=H{DVwvgAlJw1n&0jJ00a#P0JLH;8$Wp9`Sv>4USrZ_1sjM0<!q8|
zxc!{;-PUK5pso<&0dJBFgMNkp@|>Sr>@A%Wd`)WSc+I820A4sJf^=*OHfGjnC8+TD
zW$<a2;o+X=JkjtG#>Eeh?p*Mr_A}&gvx%r_lw($xcPJ>gap&bv^P|FXJr6fwvQUOh
zz&W4C8jK4zqM#Z=%N~7K_NKDXEM<K(9uVG9XDN4YijmA{1^}^dq8GAcg)r4N<3^U}
z-b$q=iUdK8Hdh73UGm8MyfcDWr5~#ja!c&dVahF^v@fRj3IazrWNc@&F~A^V)u*Pi
z`ugNogkGP(V-|+x+mvq0pc}*Bpq)By!2khN4M6SOU21NKpXW5P=UU_LoDzvrTdR;4
zU)W&&o9V6>MQOk{A%?1(Ozs{Qqlifa7!d*h2?YX1XUk3k8w>yn2@n&u^&ZXMLk+>~
z0HDxvfGrJTMY@$ys#k#t?D63Zq=W#R3eUeT(L61`VGHe?zP;`rnta#-umS)8nA%Rq

diff --git a/t/t7004/trustdb.gpg b/t/t7004/trustdb.gpg
deleted file mode 100644
index abace962b8bf84be688a6f27e4ebd0ee7052f210..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 1280
zcmZQfFGy!*W@Ke#U|?`dKkWykumMIcY@%4iM%7^n6rj+M4;MLzzlOX&pwTnxkD-}P
zc^HbXN0fL!SIq1?>env3?W^3`d(OOU5YNaX{KU(k^<0;M@87ONv)_6ZxD={-=<kYO
M2Ud3=2BC}r0AuhNr2qf`

-- 
1.7.7.498.g3f2e50

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v4 5/5] test "commit -S" and "log --show-signature"
  2011-10-20  0:36   ` [PATCH v4 0/5] Signed-commit Junio C Hamano
                       ` (3 preceding siblings ...)
  2011-10-20  0:37     ` [PATCH v4 4/5] t7004: extract generic "GPG testing" bits Junio C Hamano
@ 2011-10-20  0:37     ` Junio C Hamano
  2011-10-22  5:01     ` [PATCH 7/5] pretty: %G[?GS] placeholders Junio C Hamano
  5 siblings, 0 replies; 35+ messages in thread
From: Junio C Hamano @ 2011-10-20  0:37 UTC (permalink / raw)
  To: git

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 t/t7510-signed-commit.sh |   60 ++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 60 insertions(+), 0 deletions(-)
 create mode 100755 t/t7510-signed-commit.sh

diff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh
new file mode 100755
index 0000000..5c7475d
--- /dev/null
+++ b/t/t7510-signed-commit.sh
@@ -0,0 +1,60 @@
+#!/bin/sh
+
+test_description='signed commit tests'
+. ./test-lib.sh
+. "$TEST_DIRECTORY/lib-gpg.sh"
+
+test_expect_success GPG 'create signed commits' '
+	echo 1 >file && git add file &&
+	test_tick && git commit -S -m initial &&
+	git tag initial &&
+	git branch side &&
+
+	echo 2 >file && test_tick && git commit -a -S -m second &&
+	git tag second &&
+
+	git checkout side &&
+	echo 3 >elif && git add elif &&
+	test_tick && git commit -m "third on side" &&
+
+	git checkout master &&
+	test_tick && git merge -S side &&
+	git tag merge &&
+
+	echo 4 >file && test_tick && git commit -a -m "fourth unsigned" &&
+	git tag fourth-unsigned &&
+
+	test_tick && git commit --amend -S -m "fourth signed"
+'
+
+test_expect_success GPG 'show signatures' '
+	(
+		for commit in initial second merge master
+		do
+			git show --pretty=short --show-signature $commit >actual &&
+			grep "Good signature from" actual || exit 1
+			! grep "BAD signature from" actual || exit 1
+			echo $commit OK
+		done
+	) &&
+	(
+		for commit in merge^2 fourth-unsigned
+		do
+			git show --pretty=short --show-signature $commit >actual &&
+			grep "Good signature from" actual && exit 1
+			! grep "BAD signature from" actual || exit 1
+			echo $commit OK
+		done
+	)
+'
+
+test_expect_success GPG 'detect fudged signature' '
+	git cat-file commit master >raw &&
+	sed -e "s/fourth signed/4th forged/" raw >forged &&
+	git hash-object -w -t commit forged >forged.commit &&
+	git show --pretty=short --show-signature $(cat forged.commit) >actual &&
+	grep "BAD signature from" actual &&
+	! grep "Good signature from" actual
+'
+
+test_done
-- 
1.7.7.498.g3f2e50

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 7/5] pretty: %G[?GS] placeholders
  2011-10-20  0:36   ` [PATCH v4 0/5] Signed-commit Junio C Hamano
                       ` (4 preceding siblings ...)
  2011-10-20  0:37     ` [PATCH v4 5/5] test "commit -S" and "log --show-signature" Junio C Hamano
@ 2011-10-22  5:01     ` Junio C Hamano
  2011-10-22 10:47       ` Elia Pinto
  5 siblings, 1 reply; 35+ messages in thread
From: Junio C Hamano @ 2011-10-22  5:01 UTC (permalink / raw)
  To: git

Add new placeholders related to the GPG signature on signed commits.

 - %GG to show the raw verification message from GPG;
 - %G? to show either "G" for Good, "B" for Bad;
 - %GS to show the name of the signer.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 * The 6th is the one that works with a bogus commit with NUL in it I sent
   out previously.

   This concludes the series; I'll leave the design and implementation of
   other useful placeholders to the list for now.

 pretty.c |   86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 86 insertions(+), 0 deletions(-)

diff --git a/pretty.c b/pretty.c
index f45eb54..392d656 100644
--- a/pretty.c
+++ b/pretty.c
@@ -9,6 +9,7 @@
 #include "notes.h"
 #include "color.h"
 #include "reflog-walk.h"
+#include "gpg-interface.h"
 
 static char *user_format;
 static struct cmt_fmt_map {
@@ -640,6 +641,12 @@ struct format_commit_context {
 	const struct pretty_print_context *pretty_ctx;
 	unsigned commit_header_parsed:1;
 	unsigned commit_message_parsed:1;
+	unsigned commit_signature_parsed:1;
+	struct {
+		char *gpg_output;
+		char good_bad;
+		char *signer;
+	} signature;
 	char *message;
 	size_t width, indent1, indent2;
 
@@ -822,6 +829,59 @@ static void rewrap_message_tail(struct strbuf *sb,
 	c->indent2 = new_indent2;
 }
 
+static struct {
+	char result;
+	const char *check;
+} signature_check[] = {
+	{ 'G', ": Good signature from " },
+	{ 'B', ": BAD signature from " },
+};
+
+static void parse_signature_lines(struct format_commit_context *ctx)
+{
+	const char *buf = ctx->signature.gpg_output;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(signature_check); i++) {
+		const char *found = strstr(buf, signature_check[i].check);
+		const char *next;
+		if (!found)
+			continue;
+		ctx->signature.good_bad = signature_check[i].result;
+		found += strlen(signature_check[i].check);
+		next = strchrnul(found, '\n');
+		ctx->signature.signer = xmemdupz(found, next - found);
+		break;
+	}
+}
+
+static void parse_commit_signature(struct format_commit_context *ctx)
+{
+	struct strbuf payload = STRBUF_INIT;
+	struct strbuf signature = STRBUF_INIT;
+	struct strbuf gpg_output = STRBUF_INIT;
+	int status;
+
+	ctx->commit_signature_parsed = 1;
+
+	if (parse_signed_commit(ctx->commit->object.sha1,
+				&payload, &signature) <= 0)
+		goto out;
+	status = verify_signed_buffer(payload.buf, payload.len,
+				      signature.buf, signature.len,
+				      &gpg_output);
+	if (status && !gpg_output.len)
+		goto out;
+	ctx->signature.gpg_output = strbuf_detach(&gpg_output, NULL);
+	parse_signature_lines(ctx);
+
+ out:
+	strbuf_release(&gpg_output);
+	strbuf_release(&payload);
+	strbuf_release(&signature);
+}
+
+
 static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
 				void *context)
 {
@@ -974,6 +1034,30 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
 		return 0;
 	}
 
+	if (placeholder[0] == 'G') {
+		if (!c->commit_signature_parsed)
+			parse_commit_signature(c);
+		switch (placeholder[1]) {
+		case 'G':
+			if (c->signature.gpg_output)
+				strbuf_addstr(sb, c->signature.gpg_output);
+			break;
+		case '?':
+			switch (c->signature.good_bad) {
+			case 'G':
+			case 'B':
+				strbuf_addch(sb, c->signature.good_bad);
+			}
+			break;
+		case 'S':
+			if (c->signature.signer)
+				strbuf_addstr(sb, c->signature.signer);
+			break;
+		}
+		return 2;
+	}
+
+
 	/* For the rest we have to parse the commit header. */
 	if (!c->commit_header_parsed)
 		parse_commit_header(c);
@@ -1114,6 +1198,8 @@ void format_commit_message(const struct commit *commit,
 
 	if (context.message != commit->buffer)
 		free(context.message);
+	free(context.signature.gpg_output);
+	free(context.signature.signer);
 }
 
 static void pp_header(const struct pretty_print_context *pp,
-- 
1.7.7.555.g02edb3

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* Re: [PATCH 7/5] pretty: %G[?GS] placeholders
  2011-10-22  5:01     ` [PATCH 7/5] pretty: %G[?GS] placeholders Junio C Hamano
@ 2011-10-22 10:47       ` Elia Pinto
  2011-10-22 17:55         ` Junio C Hamano
  0 siblings, 1 reply; 35+ messages in thread
From: Elia Pinto @ 2011-10-22 10:47 UTC (permalink / raw)
  To: Junio C Hamano, git

Can you suggest what do you think can be useful placeholders ? Thanks.

2011/10/22, Junio C Hamano <gitster@pobox.com>:
> Add new placeholders related to the GPG signature on signed commits.
>
>  - %GG to show the raw verification message from GPG;
>  - %G? to show either "G" for Good, "B" for Bad;
>  - %GS to show the name of the signer.
>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>  * The 6th is the one that works with a bogus commit with NUL in it I sent
>    out previously.
>
>    This concludes the series; I'll leave the design and implementation of
>    other useful placeholders to the list for now.
>
>  pretty.c |   86
> ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 files changed, 86 insertions(+), 0 deletions(-)
>
> diff --git a/pretty.c b/pretty.c
> index f45eb54..392d656 100644
> --- a/pretty.c
> +++ b/pretty.c
> @@ -9,6 +9,7 @@
>  #include "notes.h"
>  #include "color.h"
>  #include "reflog-walk.h"
> +#include "gpg-interface.h"
>
>  static char *user_format;
>  static struct cmt_fmt_map {
> @@ -640,6 +641,12 @@ struct format_commit_context {
>  	const struct pretty_print_context *pretty_ctx;
>  	unsigned commit_header_parsed:1;
>  	unsigned commit_message_parsed:1;
> +	unsigned commit_signature_parsed:1;
> +	struct {
> +		char *gpg_output;
> +		char good_bad;
> +		char *signer;
> +	} signature;
>  	char *message;
>  	size_t width, indent1, indent2;
>
> @@ -822,6 +829,59 @@ static void rewrap_message_tail(struct strbuf *sb,
>  	c->indent2 = new_indent2;
>  }
>
> +static struct {
> +	char result;
> +	const char *check;
> +} signature_check[] = {
> +	{ 'G', ": Good signature from " },
> +	{ 'B', ": BAD signature from " },
> +};
> +
> +static void parse_signature_lines(struct format_commit_context *ctx)
> +{
> +	const char *buf = ctx->signature.gpg_output;
> +	int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(signature_check); i++) {
> +		const char *found = strstr(buf, signature_check[i].check);
> +		const char *next;
> +		if (!found)
> +			continue;
> +		ctx->signature.good_bad = signature_check[i].result;
> +		found += strlen(signature_check[i].check);
> +		next = strchrnul(found, '\n');
> +		ctx->signature.signer = xmemdupz(found, next - found);
> +		break;
> +	}
> +}
> +
> +static void parse_commit_signature(struct format_commit_context *ctx)
> +{
> +	struct strbuf payload = STRBUF_INIT;
> +	struct strbuf signature = STRBUF_INIT;
> +	struct strbuf gpg_output = STRBUF_INIT;
> +	int status;
> +
> +	ctx->commit_signature_parsed = 1;
> +
> +	if (parse_signed_commit(ctx->commit->object.sha1,
> +				&payload, &signature) <= 0)
> +		goto out;
> +	status = verify_signed_buffer(payload.buf, payload.len,
> +				      signature.buf, signature.len,
> +				      &gpg_output);
> +	if (status && !gpg_output.len)
> +		goto out;
> +	ctx->signature.gpg_output = strbuf_detach(&gpg_output, NULL);
> +	parse_signature_lines(ctx);
> +
> + out:
> +	strbuf_release(&gpg_output);
> +	strbuf_release(&payload);
> +	strbuf_release(&signature);
> +}
> +
> +
>  static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
>  				void *context)
>  {
> @@ -974,6 +1034,30 @@ static size_t format_commit_one(struct strbuf *sb,
> const char *placeholder,
>  		return 0;
>  	}
>
> +	if (placeholder[0] == 'G') {
> +		if (!c->commit_signature_parsed)
> +			parse_commit_signature(c);
> +		switch (placeholder[1]) {
> +		case 'G':
> +			if (c->signature.gpg_output)
> +				strbuf_addstr(sb, c->signature.gpg_output);
> +			break;
> +		case '?':
> +			switch (c->signature.good_bad) {
> +			case 'G':
> +			case 'B':
> +				strbuf_addch(sb, c->signature.good_bad);
> +			}
> +			break;
> +		case 'S':
> +			if (c->signature.signer)
> +				strbuf_addstr(sb, c->signature.signer);
> +			break;
> +		}
> +		return 2;
> +	}
> +
> +
>  	/* For the rest we have to parse the commit header. */
>  	if (!c->commit_header_parsed)
>  		parse_commit_header(c);
> @@ -1114,6 +1198,8 @@ void format_commit_message(const struct commit
> *commit,
>
>  	if (context.message != commit->buffer)
>  		free(context.message);
> +	free(context.signature.gpg_output);
> +	free(context.signature.signer);
>  }
>
>  static void pp_header(const struct pretty_print_context *pp,
> --
> 1.7.7.555.g02edb3
>
> --
> To unsubscribe from this list: send the line "unsubscribe git" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>

-- 
Inviato dal mio dispositivo mobile

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 7/5] pretty: %G[?GS] placeholders
  2011-10-22 10:47       ` Elia Pinto
@ 2011-10-22 17:55         ` Junio C Hamano
  0 siblings, 0 replies; 35+ messages in thread
From: Junio C Hamano @ 2011-10-22 17:55 UTC (permalink / raw)
  To: Elia Pinto; +Cc: git

Elia Pinto <gitter.spiros@gmail.com> writes:

> Can you suggest what do you think can be useful placeholders ? Thanks.

That is a weird question.

> 2011/10/22, Junio C Hamano <gitster@pobox.com>:
>> Add new placeholders related to the GPG signature on signed commits.
>>
>>  - %GG to show the raw verification message from GPG;
>>  - %G? to show either "G" for Good, "B" for Bad;
>>  - %GS to show the name of the signer.
>>
>> Signed-off-by: Junio C Hamano <gitster@pobox.com>
>> ---
>>  * The 6th is the one that works with a bogus commit with NUL in it I sent
>>    out previously.
>>
>>    This concludes the series; I'll leave the design and implementation of
>>    other useful placeholders to the list for now.

I can think of random other placeholders off the top of my head purely by
speculation without having real need [*1*], but they won't be much useful.

People on the list who *want* to use this feature in their projects may
have specific needs and they would be closer to what is needed in the real
world use cases than what comes out of thin air by imagination.

That is the reason why I left the enhancement to the list.

If you have to ask that question because you do not have any specific need
yourself, and especially if you have to ask it to *me*, then you should
wait for others to come up with their real needs, just like what I am
doing right now ;-).


[Footnote]

*1*

 - %GC that is replaced with COLOR_GREEN when Good signature is found,
   COLOR_RED when BAD signature is found, and COLOR_RESET when there is no
   signature;

 - %GD for the date the signature was made on (with date format variants);

 - %Gk for the type of the key and the Key-ID.
 

^ permalink raw reply	[flat|nested] 35+ messages in thread

end of thread, other threads:[~2011-10-22 18:04 UTC | newest]

Thread overview: 35+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-10-06  0:56 [PATCH] commit: teach --gpg-sign option Junio C Hamano
2011-10-06 15:50 ` Shawn Pearce
2011-10-06 17:11   ` Jonathan Nieder
2011-10-06 17:22     ` Matthieu Moy
2011-10-06 18:44       ` Michael J Gruber
2011-10-06 21:29     ` Junio C Hamano
2011-10-06 22:24 ` Robin H. Johnson
2011-10-07  8:40   ` Michael J Gruber
2011-10-07 11:18     ` Nguyen Thai Ngoc Duy
2011-10-09 16:32     ` Michael J Gruber
2011-10-09 22:57     ` Robin H. Johnson
2011-10-09 23:18       ` Junio C Hamano
2011-10-11  0:38         ` Robin H. Johnson
2011-10-09 20:00 ` Michael J Gruber
2011-10-09 21:22   ` Junio C Hamano
2011-10-10  6:33     ` Michael J Gruber
2011-10-10 16:35       ` Junio C Hamano
2011-10-09 22:27   ` Junio C Hamano
2011-10-10  6:33     ` Michael J Gruber
2011-10-10 16:45       ` Junio C Hamano
2011-10-11  6:39         ` Michael J Gruber
     [not found] ` <CACBZZX6xsnAv4S8zAqi08bcqrghZ8nKdzFP=UNCqZOqrEeLFnA@mail.gmail.com>
2011-10-10  4:58   ` Junio C Hamano
2011-10-19  0:20 ` [PATCH v3 0/3] Signed-commit Junio C Hamano
2011-10-19  0:20   ` [PATCH v3 1/3] Split GPG interface into its own helper library Junio C Hamano
2011-10-19  0:20   ` [PATCH v3 2/3] commit: teach --gpg-sign option Junio C Hamano
2011-10-19  0:20   ` [PATCH v3 3/3] log: --show-signature Junio C Hamano
2011-10-20  0:36   ` [PATCH v4 0/5] Signed-commit Junio C Hamano
2011-10-20  0:36     ` [PATCH v4 1/5] Split GPG interface into its own helper library Junio C Hamano
2011-10-20  0:37     ` [PATCH v4 2/5] commit: teach --gpg-sign option Junio C Hamano
2011-10-20  0:37     ` [PATCH v4 3/5] log: --show-signature Junio C Hamano
2011-10-20  0:37     ` [PATCH v4 4/5] t7004: extract generic "GPG testing" bits Junio C Hamano
2011-10-20  0:37     ` [PATCH v4 5/5] test "commit -S" and "log --show-signature" Junio C Hamano
2011-10-22  5:01     ` [PATCH 7/5] pretty: %G[?GS] placeholders Junio C Hamano
2011-10-22 10:47       ` Elia Pinto
2011-10-22 17:55         ` Junio C Hamano

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.