diff --git a/builtin/push.c b/builtin/push.c
index 35cce53..2238f4e 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -261,6 +261,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
OPT_BIT('u', "set-upstream", &flags, "set upstream for git pull/status",
TRANSPORT_PUSH_SET_UPSTREAM),
OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"),
+ OPT_BIT('s', "signed", &flags, "GPG sign the push", TRANSPORT_PUSH_SIGNED),
OPT_END()
};
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index ae164da..20b6799 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -30,12 +30,14 @@ static int receive_unpack_limit = -1;
static int transfer_unpack_limit = -1;
static int unpack_limit = 100;
static int report_status;
+static int signed_push;
static int use_sideband;
static int prefer_ofs_delta = 1;
static int auto_update_server_info;
static int auto_gc = 1;
static const char *head_name;
static int sent_capabilities;
+static char *push_certificate;
static enum deny_action parse_deny_action(const char *var, const char *value)
{@@ -114,7 +116,7 @@ static int show_ref(const char *path, const unsigned char *sha1, int flag, void
else
packet_write(1, "%s %s%c%s%s\n",
sha1_to_hex(sha1), path, 0,
- " report-status delete-refs side-band-64k",
+ " report-status delete-refs side-band-64k signed-push",
prefer_ofs_delta ? " ofs-delta" : "");
sent_capabilities = 1;
return 0;
@@ -579,6 +581,31 @@ static void check_aliased_updates(struct command *commands)
string_list_clear(&ref_list, 0);
}
+static int record_signed_push(char *cert)
+{
+ /*
+ * This is the place for you to parse the signed push
+ * certificate, grab the commit object names the push updates
+ * refs to, and append the certificate to the notes to these
+ * commits.
+ *
+ * You could also feed the signed push certificate to GPG,
+ * verify the signer identity, and all the other fun stuff,
+ * including feeding it to "pre-receive-signature" hook.
+ *
+ * Here we just throw it to stderr to demonstrate that the
+ * codepath is being exercised.
+ */
+ char *cp, *ep;
+ for (cp = cert; *cp; cp = ep) {
+ ep = strchrnul(cp, '\n');
+ if (*ep == '\n')
+ ep++;
+ fprintf(stderr, "RSP: %.*s", (int)(ep - cp), cp);
+ }
+ return 0;
+}
+
static void execute_commands(struct command *commands, const char *unpacker_error)
{
struct command *cmd;@@ -596,6 +623,12 @@ static void execute_commands(struct command *commands, const char *unpacker_erro
return;
}
+ if (push_certificate && record_signed_push(push_certificate)) {
+ for (cmd = commands; cmd; cmd = cmd->next)
+ cmd->error_string = "n/a (push signature error)";
+ return;
+ }
+
check_aliased_updates(commands);
head_name = resolve_ref("HEAD", sha1, 0, NULL);@@ -636,6 +669,8 @@ static struct command *read_head_info(void)
report_status = 1;
if (strstr(refname + reflen + 1, "side-band-64k"))
use_sideband = LARGE_PACKET_MAX;
+ if (strstr(refname + reflen + 1, "signed-push"))
+ signed_push = 1;
}
cmd = xcalloc(1, sizeof(struct command) + len - 80);
hashcpy(cmd->old_sha1, old_sha1);
@@ -731,6 +766,21 @@ static const char *unpack(void)
}
}
+static char *receive_push_certificate(void)
+{
+ struct strbuf cert = STRBUF_INIT;
+ for (;;) {
+ char line[1000];
+ int len;
+
+ len = packet_read_line(0, line, sizeof(line));
+ if (!len)
+ break;
+ strbuf_add(&cert, line, len);
+ }
+ return strbuf_detach(&cert, NULL);
+}
+
static void report(struct command *commands, const char *unpack_status)
{
struct command *cmd;@@ -846,6 +896,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
if ((commands = read_head_info()) != NULL) {
const char *unpack_status = NULL;
+ if (signed_push)
+ push_certificate = receive_push_certificate();
if (!delete_only(commands))
unpack_status = unpack();
execute_commands(commands, unpack_status);diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index 87833f4..7f4778c 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -237,6 +237,27 @@ static int sideband_demux(int in, int out, void *data)
return ret;
}
+static void sign_push_certificate(struct strbuf *cert)
+{
+ /*
+ * Here, take the contents of cert->buf, and have the user GPG
+ * sign it, and read it back in the strbuf.
+ *
+ * You may want to append some extra info to cert before giving
+ * it to GPG, possibly via a hook.
+ *
+ * Here we upcase them just to demonstrate that the codepath
+ * is being exercised.
+ */
+ char *cp;
+ for (cp = cert->buf; *cp; cp++) {
+ int ch = *cp;
+ if ('a' <= ch && ch <= 'z')
+ *cp = toupper(ch);
+ }
+ return;
+}
+
int send_pack(struct send_pack_args *args,
int fd[], struct child_process *conn,
struct ref *remote_refs,@@ -250,9 +271,11 @@ int send_pack(struct send_pack_args *args,
int allow_deleting_refs = 0;
int status_report = 0;
int use_sideband = 0;
+ int signed_push = 0;
unsigned cmds_sent = 0;
int ret;
struct async demux;
+ struct strbuf push_cert = STRBUF_INIT;
/* Does the other end support the reporting? */
if (server_supports("report-status"))@@ -270,6 +293,18 @@ int send_pack(struct send_pack_args *args,
return 0;
}
+ if (args->signed_push) {
+ if (server_supports("signed-push"))
+ signed_push = !args->dry_run;
+ else
+ warning("The receiving side does not support signed-push");
+ }
+
+ if (signed_push) {
+ strbuf_addstr(&push_cert, "Push-Certificate-Version: 0\n");
+ strbuf_addf(&push_cert, "Pusher: %s\n", git_committer_info(0));
+ }
+
/*
* Finally, tell the other end!
*/@@ -301,15 +336,19 @@ int send_pack(struct send_pack_args *args,
char *old_hex = sha1_to_hex(ref->old_sha1);
char *new_hex = sha1_to_hex(ref->new_sha1);
- if (!cmds_sent && (status_report || use_sideband)) {
- packet_buf_write(&req_buf, "%s %s %s%c%s%s",
+ if (!cmds_sent &&
+ (status_report || use_sideband || signed_push))
+ packet_buf_write(&req_buf, "%s %s %s%c%s%s%s",
old_hex, new_hex, ref->name, 0,
status_report ? " report-status" : "",
- use_sideband ? " side-band-64k" : "");
- }
+ use_sideband ? " side-band-64k" : "",
+ signed_push ? " signed-push" : "");
else
packet_buf_write(&req_buf, "%s %s %s",
old_hex, new_hex, ref->name);
+ if (signed_push)
+ strbuf_addf(&push_cert, "Update: %s %s\n",
+ new_hex, ref->name);
ref->status = status_report ?
REF_STATUS_EXPECTING_REPORT :
REF_STATUS_OK;@@ -326,6 +365,23 @@ int send_pack(struct send_pack_args *args,
safe_write(out, req_buf.buf, req_buf.len);
packet_flush(out);
}
+
+ if (signed_push && cmds_sent) {
+ char *cp, *ep;
+
+ sign_push_certificate(&push_cert);
+ strbuf_reset(&req_buf);
+ for (cp = push_cert.buf; *cp; cp = ep) {
+ ep = strchrnul(cp, '\n');
+ if (*ep == '\n')
+ ep++;
+ packet_buf_write(&req_buf, "%.*s",
+ (int)(ep - cp), cp);
+ }
+ /* Do we need anything funky for stateless rpc? */
+ safe_write(out, req_buf.buf, req_buf.len);
+ packet_flush(out);
+ }
strbuf_release(&req_buf);
if (use_sideband && cmds_sent) {diff --git a/send-pack.h b/send-pack.h
index 05d7ab1..754943e 100644
--- a/send-pack.h
+++ b/send-pack.h
@@ -11,6 +11,7 @@ struct send_pack_args {
use_thin_pack:1,
use_ofs_delta:1,
dry_run:1,
+ signed_push:1,
stateless_rpc:1;
};
diff --git a/transport.c b/transport.c
index fa279d5..7a7ffe4 100644
--- a/transport.c
+++ b/transport.c
@@ -476,6 +476,9 @@ static int set_git_option(struct git_transport_options *opts,
else
opts->depth = atoi(value);
return 0;
+ } else if (!strcmp(name, TRANS_OPT_SIGNED_PUSH)) {
+ opts->signed_push = !!value;
+ return 0;
}
return 1;
}@@ -793,6 +796,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
args.progress = transport->progress;
args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
args.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN);
+ args.signed_push = !!(flags & TRANSPORT_PUSH_SIGNED);
ret = send_pack(&args, data->fd, data->conn, remote_refs,
&data->extra_have);
diff --git a/transport.h b/transport.h
index 059b330..d2fa478 100644
--- a/transport.h
+++ b/transport.h
@@ -8,6 +8,7 @@ struct git_transport_options {
unsigned thin : 1;
unsigned keep : 1;
unsigned followtags : 1;
+ unsigned signed_push : 1;
int depth;
const char *uploadpack;
const char *receivepack;@@ -102,6 +103,7 @@ struct transport {
#define TRANSPORT_PUSH_PORCELAIN 16
#define TRANSPORT_PUSH_SET_UPSTREAM 32
#define TRANSPORT_RECURSE_SUBMODULES_CHECK 64
+#define TRANSPORT_PUSH_SIGNED 128
#define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
@@ -128,6 +130,8 @@ struct transport *transport_get(struct remote *, const char *);
/* Aggressively fetch annotated tags if possible */
#define TRANS_OPT_FOLLOWTAGS "followtags"
+#define TRANS_OPT_SIGNED_PUSH "signedpush"
+
/**
* Returns 0 if the option was used, non-zero otherwise. Prints a
* message to stderr if the option is not used.
--
1.7.7.rc0.188.g3793ac