Merge branch 'jc/show-sig'

* jc/show-sig:
  log --show-signature: reword the common two-head merge case
  log-tree: show mergetag in log --show-signature output
  log-tree.c: small refactor in show_signature()
  commit --amend -S: strip existing gpgsig headers
  verify_signed_buffer: fix stale comment
  gpg-interface: allow use of a custom GPG binary
  pretty: %G[?GS] placeholders
  test "commit -S" and "log --show-signature"
  log: --show-signature
  commit: teach --gpg-sign option

Conflicts:
	builtin/commit-tree.c
	builtin/commit.c
	builtin/merge.c
	notes-cache.c
	pretty.c
This commit is contained in:
Junio C Hamano 2012-01-06 12:44:07 -08:00
commit 5de89d3abf
16 changed files with 488 additions and 34 deletions

View File

@ -99,10 +99,6 @@ Unless otherwise noted, all the fixes since v1.7.8 in the maintenance
releases are contained in this release (see release notes to them for releases are contained in this release (see release notes to them for
details). details).
* gitweb did not correctly fall back to configured $fallback_encoding
that is not 'latin1'.
(merge b13e3ea jn/maint-gitweb-utf8-fix later to maint).
-- --
exec >/var/tmp/1 exec >/var/tmp/1
O=v1.7.8.2-301-g48de656 O=v1.7.8.2-301-g48de656

View File

@ -1123,6 +1123,17 @@ grep.lineNumber::
grep.extendedRegexp:: grep.extendedRegexp::
If set to true, enable '--extended-regexp' option by default. If set to true, enable '--extended-regexp' option by default.
gpg.program::
Use this custom program instead of "gpg" found on $PATH when
making or verifying a PGP signature. The program must support the
same command line interface as GPG, namely, to verify a detached
signature, "gpg --verify $file - <$signature" is run, and the
program is expected to signal a good signature by exiting with
code 0, and to generate an ascii-armored detached signature, the
standard input of "gpg -bsau $key" is fed with the contents to be
signed, and the program is expected to send the result to its
standard output.
gui.commitmsgwidth:: gui.commitmsgwidth::
Defines how wide the commit message window is in the Defines how wide the commit message window is in the
linkgit:git-gui[1]. "75" is the default. linkgit:git-gui[1]. "75" is the default.

View File

@ -38,7 +38,9 @@ created (i.e. a lightweight tag).
A GnuPG signed tag object will be created when `-s` or `-u A GnuPG signed tag object will be created when `-s` or `-u
<key-id>` is used. When `-u <key-id>` is not used, the <key-id>` is used. When `-u <key-id>` is not used, the
committer identity for the current user is used to find the committer identity for the current user is used to find the
GnuPG key for signing. GnuPG key for signing. The configuration variable `gpg.program`
is used to specify custom GnuPG binary.
OPTIONS OPTIONS
------- -------
@ -48,11 +50,11 @@ OPTIONS
-s:: -s::
--sign:: --sign::
Make a GPG-signed tag, using the default e-mail address's key Make a GPG-signed tag, using the default e-mail address's key.
-u <key-id>:: -u <key-id>::
--local-user=<key-id>:: --local-user=<key-id>::
Make a GPG-signed tag, using the given key Make a GPG-signed tag, using the given key.
-f:: -f::
--force:: --force::

View File

@ -8,8 +8,9 @@
#include "tree.h" #include "tree.h"
#include "builtin.h" #include "builtin.h"
#include "utf8.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) 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); 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 cmd_commit_tree(int argc, const char **argv, const char *prefix)
{ {
int i, got_tree = 0; 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 tree_sha1[20];
unsigned char commit_sha1[20]; unsigned char commit_sha1[20];
struct strbuf buffer = STRBUF_INIT; 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")) if (argc < 2 || !strcmp(argv[1], "-h"))
usage(commit_tree_usage); 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++) { for (i = 1; i < argc; i++) {
const char *arg = argv[i]; const char *arg = argv[i];
if (!strcmp(arg, "-p")) { if (!strcmp(arg, "-p")) {
@ -51,6 +64,11 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
continue; continue;
} }
if (!memcmp(arg, "-S", 2)) {
sign_commit = arg + 2;
continue;
}
if (!strcmp(arg, "-m")) { if (!strcmp(arg, "-m")) {
if (argc <= ++i) if (argc <= ++i)
usage(commit_tree_usage); 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"); die_errno("git commit-tree: failed to read");
} }
if (commit_tree(&buffer, tree_sha1, parents, commit_sha1, NULL)) { if (commit_tree(&buffer, tree_sha1, parents, commit_sha1,
NULL, sign_commit)) {
strbuf_release(&buffer); strbuf_release(&buffer);
return 1; return 1;
} }

View File

@ -26,6 +26,7 @@
#include "unpack-trees.h" #include "unpack-trees.h"
#include "quote.h" #include "quote.h"
#include "submodule.h" #include "submodule.h"
#include "gpg-interface.h"
static const char * const builtin_commit_usage[] = { static const char * const builtin_commit_usage[] = {
"git commit [options] [--] <filepattern>...", "git commit [options] [--] <filepattern>...",
@ -86,6 +87,8 @@ static int edit_flag = -1; /* unspecified */
static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
static int no_post_rewrite, allow_empty_message; static int no_post_rewrite, allow_empty_message;
static char *untracked_files_arg, *force_date, *ignore_submodule_arg; static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
static char *sign_commit;
/* /*
* The default commit message cleanup mode will remove the lines * The default commit message cleanup mode will remove the lines
* beginning with # (shell comments) and leading and trailing * beginning with # (shell comments) and leading and trailing
@ -145,6 +148,8 @@ static struct option builtin_commit_options[] = {
OPT_BOOL('e', "edit", &edit_flag, "force edit of commit"), OPT_BOOL('e', "edit", &edit_flag, "force edit of commit"),
OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"), 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"), 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 */ /* end commit message options */
OPT_GROUP("Commit contents options"), OPT_GROUP("Commit contents options"),
@ -1325,6 +1330,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) static int git_commit_config(const char *k, const char *v, void *cb)
{ {
struct wt_status *s = cb; struct wt_status *s = cb;
int status;
if (!strcmp(k, "commit.template")) if (!strcmp(k, "commit.template"))
return git_config_pathname(&template_file, k, v); return git_config_pathname(&template_file, k, v);
@ -1333,6 +1339,9 @@ static int git_commit_config(const char *k, const char *v, void *cb)
return 0; return 0;
} }
status = git_gpg_config(k, v, NULL);
if (status)
return status;
return git_status_config(k, v, s); return git_status_config(k, v, s);
} }
@ -1486,14 +1495,15 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
} }
if (amend) { if (amend) {
extra = read_commit_extra_headers(current_head); const char *exclude_gpgsig[2] = { "gpgsig", NULL };
extra = read_commit_extra_headers(current_head, exclude_gpgsig);
} else { } else {
struct commit_extra_header **tail = &extra; struct commit_extra_header **tail = &extra;
append_merge_tag_headers(parents, &tail); append_merge_tag_headers(parents, &tail);
} }
if (commit_tree_extended(&sb, active_cache_tree->sha1, parents, sha1, if (commit_tree_extended(&sb, active_cache_tree->sha1, parents, sha1,
author_ident.buf, extra)) { author_ident.buf, sign_commit, extra)) {
rollback_index_files(); rollback_index_files();
die(_("failed to write commit object")); die(_("failed to write commit object"));
} }

View File

@ -27,6 +27,7 @@
#include "resolve-undo.h" #include "resolve-undo.h"
#include "remote.h" #include "remote.h"
#include "fmt-merge-msg.h" #include "fmt-merge-msg.h"
#include "gpg-interface.h"
#define DEFAULT_TWOHEAD (1<<0) #define DEFAULT_TWOHEAD (1<<0)
#define DEFAULT_OCTOPUS (1<<1) #define DEFAULT_OCTOPUS (1<<1)
@ -64,6 +65,7 @@ static int allow_rerere_auto;
static int abort_current_merge; static int abort_current_merge;
static int show_progress = -1; static int show_progress = -1;
static int default_to_upstream; static int default_to_upstream;
static const char *sign_commit;
static struct strategy all_strategy[] = { static struct strategy all_strategy[] = {
{ "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL }, { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL },
@ -209,6 +211,8 @@ static struct option builtin_merge_options[] = {
OPT_BOOLEAN(0, "abort", &abort_current_merge, OPT_BOOLEAN(0, "abort", &abort_current_merge,
"abort the current in-progress merge"), "abort the current in-progress merge"),
OPT_SET_INT(0, "progress", &show_progress, "force progress reporting", 1), 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_BOOLEAN(0, "overwrite-ignore", &overwrite_ignore, "update ignored files (default)"), OPT_BOOLEAN(0, "overwrite-ignore", &overwrite_ignore, "update ignored files (default)"),
OPT_END() OPT_END()
}; };
@ -571,7 +575,11 @@ static int git_merge_config(const char *k, const char *v, void *cb)
default_to_upstream = git_config_bool(k, v); default_to_upstream = git_config_bool(k, v);
return 0; return 0;
} }
status = fmt_merge_msg_config(k, v, cb); status = fmt_merge_msg_config(k, v, cb);
if (status)
return status;
status = git_gpg_config(k, v, NULL);
if (status) if (status)
return status; return status;
return git_diff_ui_config(k, v, cb); return git_diff_ui_config(k, v, cb);
@ -910,7 +918,8 @@ static int merge_trivial(struct commit *head)
parent->next->item = remoteheads->item; parent->next->item = remoteheads->item;
parent->next->next = NULL; parent->next->next = NULL;
prepare_to_commit(); prepare_to_commit();
if (commit_tree(&merge_msg, result_tree, parent, result_commit, NULL)) if (commit_tree(&merge_msg, result_tree, parent, result_commit, NULL,
sign_commit))
die(_("failed to write commit object")); die(_("failed to write commit object"));
finish(head, result_commit, "In-index merge"); finish(head, result_commit, "In-index merge");
drop_save(); drop_save();
@ -942,7 +951,8 @@ static int finish_automerge(struct commit *head,
strbuf_addch(&merge_msg, '\n'); strbuf_addch(&merge_msg, '\n');
prepare_to_commit(); prepare_to_commit();
free_commit_list(remoteheads); free_commit_list(remoteheads);
if (commit_tree(&merge_msg, result_tree, parents, result_commit, NULL)) if (commit_tree(&merge_msg, result_tree, parents, result_commit,
NULL, sign_commit))
die(_("failed to write commit object")); die(_("failed to write commit object"));
strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy); strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
finish(head, result_commit, buf.buf); finish(head, result_commit, buf.buf);

118
commit.c
View File

@ -6,6 +6,7 @@
#include "diff.h" #include "diff.h"
#include "revision.h" #include "revision.h"
#include "notes.h" #include "notes.h"
#include "gpg-interface.h"
int save_commit_buffer = 1; int save_commit_buffer = 1;
@ -840,6 +841,86 @@ struct commit_list *reduce_heads(struct commit_list *heads)
return result; 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;
}
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_signature, saw_signature = -1;
char *line, *tail;
if (!buffer || type != OBJ_COMMIT)
goto cleanup;
line = buffer;
tail = buffer + size;
in_signature = 0;
saw_signature = 0;
while (line < tail) {
const char *sig = NULL;
char *next = memchr(line, '\n', tail - line);
next = next ? next + 1 : tail;
if (in_signature && line[0] == ' ')
sig = line + 1;
else if (!prefixcmp(line, gpg_sig_header) &&
line[gpg_sig_header_len] == ' ')
sig = line + gpg_sig_header_len + 1;
if (sig) {
strbuf_add(signature, sig, next - sig);
saw_signature = 1;
in_signature = 1;
} else {
if (*line == '\n')
/* dump the whole remainder of the buffer */
next = tail;
strbuf_add(payload, line, next - line);
in_signature = 0;
}
line = next;
}
cleanup:
free(buffer);
return saw_signature;
}
static void handle_signed_tag(struct commit *parent, struct commit_extra_header ***tail) static void handle_signed_tag(struct commit *parent, struct commit_extra_header ***tail)
{ {
struct merge_remote_desc *desc; struct merge_remote_desc *desc;
@ -900,14 +981,15 @@ static void add_extra_header(struct strbuf *buffer,
strbuf_addch(buffer, '\n'); strbuf_addch(buffer, '\n');
} }
struct commit_extra_header *read_commit_extra_headers(struct commit *commit) struct commit_extra_header *read_commit_extra_headers(struct commit *commit,
const char **exclude)
{ {
struct commit_extra_header *extra = NULL; struct commit_extra_header *extra = NULL;
unsigned long size; unsigned long size;
enum object_type type; enum object_type type;
char *buffer = read_sha1_file(commit->object.sha1, &type, &size); char *buffer = read_sha1_file(commit->object.sha1, &type, &size);
if (buffer && type == OBJ_COMMIT) if (buffer && type == OBJ_COMMIT)
extra = read_commit_extra_header_lines(buffer, size); extra = read_commit_extra_header_lines(buffer, size, exclude);
free(buffer); free(buffer);
return extra; return extra;
} }
@ -921,7 +1003,23 @@ static inline int standard_header_field(const char *field, size_t len)
(len == 8 && !memcmp(field, "encoding ", 9))); (len == 8 && !memcmp(field, "encoding ", 9)));
} }
struct commit_extra_header *read_commit_extra_header_lines(const char *buffer, size_t size) static int excluded_header_field(const char *field, size_t len, const char **exclude)
{
if (!exclude)
return 0;
while (*exclude) {
size_t xlen = strlen(*exclude);
if (len == xlen &&
!memcmp(field, *exclude, xlen) && field[xlen] == ' ')
return 1;
exclude++;
}
return 0;
}
struct commit_extra_header *read_commit_extra_header_lines(const char *buffer, size_t size,
const char **exclude)
{ {
struct commit_extra_header *extra = NULL, **tail = &extra, *it = NULL; struct commit_extra_header *extra = NULL, **tail = &extra, *it = NULL;
const char *line, *next, *eof, *eob; const char *line, *next, *eof, *eob;
@ -947,7 +1045,8 @@ struct commit_extra_header *read_commit_extra_header_lines(const char *buffer, s
if (next <= eof) if (next <= eof)
eof = next; eof = next;
if (standard_header_field(line, eof - line)) if (standard_header_field(line, eof - line) ||
excluded_header_field(line, eof - line, exclude))
continue; continue;
it = xcalloc(1, sizeof(*it)); it = xcalloc(1, sizeof(*it));
@ -975,13 +1074,14 @@ void free_commit_extra_headers(struct commit_extra_header *extra)
int commit_tree(const struct strbuf *msg, unsigned char *tree, int commit_tree(const struct strbuf *msg, unsigned char *tree,
struct commit_list *parents, unsigned char *ret, 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; struct commit_extra_header *extra = NULL, **tail = &extra;
int result; int result;
append_merge_tag_headers(parents, &tail); 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); free_commit_extra_headers(extra);
return result; return result;
} }
@ -993,7 +1093,8 @@ static const char commit_utf8_warn[] =
int commit_tree_extended(const struct strbuf *msg, unsigned char *tree, int commit_tree_extended(const struct strbuf *msg, unsigned char *tree,
struct commit_list *parents, unsigned char *ret, 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 result;
int encoding_is_utf8; int encoding_is_utf8;
@ -1046,6 +1147,9 @@ int commit_tree_extended(const struct strbuf *msg, unsigned char *tree,
if (encoding_is_utf8 && !is_utf8(buffer.buf)) if (encoding_is_utf8 && !is_utf8(buffer.buf))
fprintf(stderr, commit_utf8_warn); 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); result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret);
strbuf_release(&buffer); strbuf_release(&buffer);
return result; return result;

View File

@ -193,15 +193,15 @@ extern void append_merge_tag_headers(struct commit_list *parents,
extern int commit_tree(const struct strbuf *msg, unsigned char *tree, extern int commit_tree(const struct strbuf *msg, unsigned char *tree,
struct commit_list *parents, unsigned char *ret, struct commit_list *parents, unsigned char *ret,
const char *author); const char *author, const char *sign_commit);
extern int commit_tree_extended(const struct strbuf *msg, unsigned char *tree, extern int commit_tree_extended(const struct strbuf *msg, unsigned char *tree,
struct commit_list *parents, unsigned char *ret, struct commit_list *parents, unsigned char *ret,
const char *author, const char *author, const char *sign_commit,
struct commit_extra_header *); struct commit_extra_header *);
extern struct commit_extra_header *read_commit_extra_headers(struct commit *); extern struct commit_extra_header *read_commit_extra_headers(struct commit *, const char **);
extern struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len); extern struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
extern void free_commit_extra_headers(struct commit_extra_header *extra); extern void free_commit_extra_headers(struct commit_extra_header *extra);
@ -218,4 +218,6 @@ struct merge_remote_desc {
*/ */
struct commit *get_merge_parent(const char *name); struct commit *get_merge_parent(const char *name);
extern int parse_signed_commit(const unsigned char *sha1,
struct strbuf *message, struct strbuf *signature);
#endif /* COMMIT_H */ #endif /* COMMIT_H */

View File

@ -5,6 +5,7 @@
#include "sigchain.h" #include "sigchain.h"
static char *configured_signing_key; static char *configured_signing_key;
static const char *gpg_program = "gpg";
void set_signing_key(const char *key) void set_signing_key(const char *key)
{ {
@ -15,9 +16,12 @@ void set_signing_key(const char *key)
int git_gpg_config(const char *var, const char *value, void *cb) int git_gpg_config(const char *var, const char *value, void *cb)
{ {
if (!strcmp(var, "user.signingkey")) { if (!strcmp(var, "user.signingkey")) {
set_signing_key(value);
}
if (!strcmp(var, "gpg.program")) {
if (!value) if (!value)
return config_error_nonbool(var); return config_error_nonbool(var);
set_signing_key(value); gpg_program = xstrdup(value);
} }
return 0; return 0;
} }
@ -46,7 +50,7 @@ int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *sig
gpg.argv = args; gpg.argv = args;
gpg.in = -1; gpg.in = -1;
gpg.out = -1; gpg.out = -1;
args[0] = "gpg"; args[0] = gpg_program;
args[1] = "-bsau"; args[1] = "-bsau";
args[2] = signing_key; args[2] = signing_key;
args[3] = NULL; args[3] = NULL;
@ -91,20 +95,18 @@ int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *sig
/* /*
* Run "gpg" to see if the payload matches the detached signature. * Run "gpg" to see if the payload matches the detached signature.
* gpg_output_to tells where the output from "gpg" should go: * gpg_output, when set, receives the diagnostic output from GPG.
* < 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, int verify_signed_buffer(const char *payload, size_t payload_size,
const char *signature, size_t signature_size, const char *signature, size_t signature_size,
struct strbuf *gpg_output) struct strbuf *gpg_output)
{ {
struct child_process gpg; struct child_process gpg;
const char *args_gpg[] = {"gpg", "--verify", "FILE", "-", NULL}; const char *args_gpg[] = {NULL, "--verify", "FILE", "-", NULL};
char path[PATH_MAX]; char path[PATH_MAX];
int fd, ret; int fd, ret;
args_gpg[0] = gpg_program;
fd = git_mkstemp(path, PATH_MAX, ".git_vtag_tmpXXXXXX"); fd = git_mkstemp(path, PATH_MAX, ".git_vtag_tmpXXXXXX");
if (fd < 0) if (fd < 0)
return error("could not create temporary file '%s': %s", return error("could not create temporary file '%s': %s",

View File

@ -8,6 +8,7 @@
#include "refs.h" #include "refs.h"
#include "string-list.h" #include "string-list.h"
#include "color.h" #include "color.h"
#include "gpg-interface.h"
struct decoration name_decoration = { "object names" }; struct decoration name_decoration = { "object names" };
@ -403,6 +404,129 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit,
*extra_headers_p = extra_headers; *extra_headers_p = extra_headers;
} }
static void show_sig_lines(struct rev_info *opt, int status, const char *bol)
{
const char *color, *reset, *eol;
color = diff_get_color_opt(&opt->diffopt,
status ? DIFF_WHITESPACE : DIFF_FRAGINFO);
reset = diff_get_color_opt(&opt->diffopt, DIFF_RESET);
while (*bol) {
eol = strchrnul(bol, '\n');
printf("%s%.*s%s%s", color, (int)(eol - bol), bol, reset,
*eol ? "\n" : "");
bol = (*eol) ? (eol + 1) : eol;
}
}
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;
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");
show_sig_lines(opt, status, gpg_output.buf);
out:
strbuf_release(&gpg_output);
strbuf_release(&payload);
strbuf_release(&signature);
}
static int which_parent(const unsigned char *sha1, const struct commit *commit)
{
int nth;
const struct commit_list *parent;
for (nth = 0, parent = commit->parents; parent; parent = parent->next) {
if (!hashcmp(parent->item->object.sha1, sha1))
return nth;
nth++;
}
return -1;
}
static int is_common_merge(const struct commit *commit)
{
return (commit->parents
&& commit->parents->next
&& !commit->parents->next->next);
}
static void show_one_mergetag(struct rev_info *opt,
struct commit_extra_header *extra,
struct commit *commit)
{
unsigned char sha1[20];
struct tag *tag;
struct strbuf verify_message;
int status, nth;
size_t payload_size, gpg_message_offset;
hash_sha1_file(extra->value, extra->len, typename(OBJ_TAG), sha1);
tag = lookup_tag(sha1);
if (!tag)
return; /* error message already given */
strbuf_init(&verify_message, 256);
if (parse_tag_buffer(tag, extra->value, extra->len))
strbuf_addstr(&verify_message, "malformed mergetag\n");
else if (is_common_merge(commit) &&
!hashcmp(tag->tagged->sha1,
commit->parents->next->item->object.sha1))
strbuf_addf(&verify_message,
"merged tag '%s'\n", tag->tag);
else if ((nth = which_parent(tag->tagged->sha1, commit)) < 0)
strbuf_addf(&verify_message, "tag %s names a non-parent %s\n",
tag->tag, tag->tagged->sha1);
else
strbuf_addf(&verify_message,
"parent #%d, tagged '%s'\n", nth + 1, tag->tag);
gpg_message_offset = verify_message.len;
payload_size = parse_signature(extra->value, extra->len);
if ((extra->len <= payload_size) ||
(verify_signed_buffer(extra->value, payload_size,
extra->value + payload_size,
extra->len - payload_size,
&verify_message) &&
verify_message.len <= gpg_message_offset)) {
strbuf_addstr(&verify_message, "No signature\n");
status = -1;
}
else if (strstr(verify_message.buf + gpg_message_offset,
": Good signature from "))
status = 0;
else
status = -1;
show_sig_lines(opt, status, verify_message.buf);
strbuf_release(&verify_message);
}
static void show_mergetag(struct rev_info *opt, struct commit *commit)
{
struct commit_extra_header *extra, *to_free;
to_free = read_commit_extra_headers(commit, NULL);
for (extra = to_free; extra; extra = extra->next) {
if (strcmp(extra->key, "mergetag"))
continue; /* not a merge tag */
show_one_mergetag(opt, extra, commit);
}
free_commit_extra_headers(to_free);
}
void show_log(struct rev_info *opt) void show_log(struct rev_info *opt)
{ {
struct strbuf msgbuf = STRBUF_INIT; struct strbuf msgbuf = STRBUF_INIT;
@ -514,6 +638,11 @@ void show_log(struct rev_info *opt)
} }
} }
if (opt->show_signature) {
show_signature(opt, commit);
show_mergetag(opt, commit);
}
if (!commit->buffer) if (!commit->buffer)
return; return;

View File

@ -59,7 +59,7 @@ int notes_cache_write(struct notes_cache *c)
return -1; return -1;
strbuf_attach(&msg, c->validity, strbuf_attach(&msg, c->validity,
strlen(c->validity), strlen(c->validity) + 1); strlen(c->validity), strlen(c->validity) + 1);
if (commit_tree(&msg, tree_sha1, NULL, commit_sha1, NULL) < 0) if (commit_tree(&msg, tree_sha1, NULL, commit_sha1, NULL, NULL) < 0)
return -1; return -1;
if (update_ref("update notes cache", c->tree.ref, commit_sha1, NULL, if (update_ref("update notes cache", c->tree.ref, commit_sha1, NULL,
0, QUIET_ON_ERR) < 0) 0, QUIET_ON_ERR) < 0)

View File

@ -551,7 +551,7 @@ void create_notes_commit(struct notes_tree *t, struct commit_list *parents,
/* else: t->ref points to nothing, assume root/orphan commit */ /* 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"); die("Failed to commit notes tree to database");
} }

View File

@ -9,6 +9,7 @@
#include "notes.h" #include "notes.h"
#include "color.h" #include "color.h"
#include "reflog-walk.h" #include "reflog-walk.h"
#include "gpg-interface.h"
static char *user_format; static char *user_format;
static struct cmt_fmt_map { static struct cmt_fmt_map {
@ -640,6 +641,12 @@ struct format_commit_context {
const struct pretty_print_context *pretty_ctx; const struct pretty_print_context *pretty_ctx;
unsigned commit_header_parsed:1; unsigned commit_header_parsed:1;
unsigned commit_message_parsed:1; unsigned commit_message_parsed:1;
unsigned commit_signature_parsed:1;
struct {
char *gpg_output;
char good_bad;
char *signer;
} signature;
char *message; char *message;
size_t width, indent1, indent2; size_t width, indent1, indent2;
@ -822,6 +829,59 @@ static void rewrap_message_tail(struct strbuf *sb,
c->indent2 = new_indent2; 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 int format_reflog_person(struct strbuf *sb, static int format_reflog_person(struct strbuf *sb,
char part, char part,
struct reflog_walk_info *log, struct reflog_walk_info *log,
@ -999,6 +1059,30 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
return 0; 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. */ /* For the rest we have to parse the commit header. */
if (!c->commit_header_parsed) if (!c->commit_header_parsed)
parse_commit_header(c); parse_commit_header(c);
@ -1141,6 +1225,8 @@ void format_commit_message(const struct commit *commit,
if (context.message != commit->buffer) if (context.message != commit->buffer)
free(context.message); free(context.message);
free(context.signature.gpg_output);
free(context.signature.signer);
} }
static void pp_header(const struct pretty_print_context *pp, static void pp_header(const struct pretty_print_context *pp,

View File

@ -1469,6 +1469,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
revs->show_notes = 1; revs->show_notes = 1;
revs->show_notes_given = 1; revs->show_notes_given = 1;
revs->notes_opt.use_default_notes = 1; revs->notes_opt.use_default_notes = 1;
} else if (!strcmp(arg, "--show-signature")) {
revs->show_signature = 1;
} else if (!prefixcmp(arg, "--show-notes=") || } else if (!prefixcmp(arg, "--show-notes=") ||
!prefixcmp(arg, "--notes=")) { !prefixcmp(arg, "--notes=")) {
struct strbuf buf = STRBUF_INIT; struct strbuf buf = STRBUF_INIT;

View File

@ -110,6 +110,7 @@ struct rev_info {
show_merge:1, show_merge:1,
show_notes:1, show_notes:1,
show_notes_given:1, show_notes_given:1,
show_signature:1,
pretty_given:1, pretty_given:1,
abbrev_commit:1, abbrev_commit:1,
abbrev_commit_given:1, abbrev_commit_given:1,

80
t/t7510-signed-commit.sh Executable file
View File

@ -0,0 +1,80 @@
#!/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" &&
git tag 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 >forged1 &&
git hash-object -w -t commit forged1 >forged1.commit &&
git show --pretty=short --show-signature $(cat forged1.commit) >actual1 &&
grep "BAD signature from" actual1 &&
! grep "Good signature from" actual1
'
test_expect_success GPG 'detect fudged signature with NUL' '
git cat-file commit master >raw &&
cat raw >forged2 &&
echo Qwik | tr "Q" "\000" >>forged2 &&
git hash-object -w -t commit forged2 >forged2.commit &&
git show --pretty=short --show-signature $(cat forged2.commit) >actual2 &&
grep "BAD signature from" actual2 &&
! grep "Good signature from" actual2
'
test_expect_success GPG 'amending already signed commit' '
git checkout fourth-signed^0 &&
git commit --amend -S --no-edit &&
git show -s --show-signature HEAD >actual &&
grep "Good signature from" actual &&
! grep "BAD signature from" actual
'
test_done