commit: teach --gpg-sign option

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

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

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

The lines of GPG detached signature are placed in a new multi-line header
field, instead of tucking the signature block at the end of the commit log
message text (similar to how signed tag is done), for multiple reasons:

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

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

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

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

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

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

     iQIcBAABAgAGBQJOjPtrAAoJELC16IaWr+bL4TMP/RSe2Y/jYnCkds9unO5JEnfG
     ...
     =dt98
     -----END PGP SIGNATURE-----

    foo

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

Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Junio C Hamano 2011-10-05 17:23:20 -07:00
parent 96b8d93a53
commit ba3c69a9ee
7 changed files with 95 additions and 13 deletions

View File

@ -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 [(-p <sha1>)...] [-m <message>] [-F <file>] <sha1> <changelog";
static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-S<signer>] [-m <message>] [-F <file>] <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, got_tree = 0;
@ -32,12 +41,16 @@ 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 (get_sha1(argv[1], tree_sha1))
die("Not a valid object name %s", argv[1]);
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
if (!strcmp(arg, "-p")) {
@ -51,6 +64,11 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
continue;
}
if (!memcmp(arg, "-S", 2)) {
sign_commit = arg + 2;
continue;
}
if (!strcmp(arg, "-m")) {
if (argc <= ++i)
usage(commit_tree_usage);
@ -98,7 +116,8 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
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;
}

View File

@ -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"),
@ -1324,6 +1329,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);
@ -1332,6 +1338,9 @@ static int git_commit_config(const char *k, const char *v, void *cb)
return 0;
}
status = git_gpg_config(k, v, NULL);
if (status)
return status;
return git_status_config(k, v, s);
}
@ -1488,7 +1497,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
extra = read_commit_extra_headers(current_head);
if (commit_tree_extended(sb.buf, active_cache_tree->sha1, parents, sha1,
author_ident.buf, extra)) {
author_ident.buf, sign_commit, extra)) {
rollback_index_files();
die(_("failed to write commit object"));
}

View File

@ -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)
@ -62,6 +63,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 },
@ -207,6 +209,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()
};
@ -533,6 +537,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")) {
@ -570,6 +576,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);
}
@ -902,7 +912,8 @@ static int merge_trivial(struct commit *head)
parent->next->item = remoteheads->item;
parent->next->next = NULL;
prepare_to_commit();
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(head, result_commit, "In-index merge");
drop_save();
return 0;
@ -933,7 +944,8 @@ static int finish_automerge(struct commit *head,
strbuf_addch(&merge_msg, '\n');
prepare_to_commit();
free_commit_list(remoteheads);
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(head, result_commit, buf.buf);
strbuf_release(&buf);

View File

@ -6,6 +6,7 @@
#include "diff.h"
#include "revision.h"
#include "notes.h"
#include "gpg-interface.h"
int save_commit_buffer = 1;
@ -840,6 +841,42 @@ 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;
if (!copypos) {
strbuf_insert(buf, inspos, gpg_sig_header, gpg_sig_header_len);
inspos += gpg_sig_header_len;
}
strbuf_insert(buf, inspos++, " ", 1);
strbuf_insert(buf, inspos, bol, len);
inspos += len;
copypos += len;
}
strbuf_release(&sig);
return 0;
}
static void handle_signed_tag(struct commit *parent, struct commit_extra_header ***tail)
{
struct merge_remote_desc *desc;
@ -975,13 +1012,14 @@ void free_commit_extra_headers(struct commit_extra_header *extra)
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)
{
struct commit_extra_header *extra = NULL, **tail = &extra;
int result;
append_merge_tag_headers(parents, &tail);
result = commit_tree_extended(msg, tree, parents, ret, author, extra);
result = commit_tree_extended(msg, tree, parents, ret,
author, sign_commit, extra);
free_commit_extra_headers(extra);
return result;
}
@ -993,7 +1031,8 @@ static const char commit_utf8_warn[] =
int commit_tree_extended(const char *msg, unsigned char *tree,
struct commit_list *parents, unsigned char *ret,
const char *author, struct commit_extra_header *extra)
const char *author, const char *sign_commit,
struct commit_extra_header *extra)
{
int result;
int encoding_is_utf8;
@ -1043,6 +1082,9 @@ int commit_tree_extended(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;

View File

@ -193,11 +193,11 @@ extern void append_merge_tag_headers(struct commit_list *parents,
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);
extern int commit_tree_extended(const char *msg, unsigned char *tree,
struct commit_list *parents, unsigned char *ret,
const char *author,
const char *author, const char *sign_commit,
struct commit_extra_header *);
extern struct commit_extra_header *read_commit_extra_headers(struct commit *);

View File

@ -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)

View File

@ -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");
}