Merge branch 'jx/proc-receive-hook' into pu

"git receive-pack" that accepts requests by "git push" learned to
outsource most of the ref updates to the new "proc-receive" hook.

* jx/proc-receive-hook:
  doc: add documentation for the proc-receive hook
  transport: parse report options for tracking refs
  t5411: test updates of remote-tracking branches
  receive-pack: new config receive.procReceiveRefs
  refs.c: refactor to reuse ref_is_hidden()
  receive-pack: feed report options to post-receive
  doc: add document for capability report-status-v2
  New capability "report-status-v2" for git-push
  receive-pack: add new proc-receive hook
  t5411: add basic test cases for proc-receive hook
  transport: not report a non-head push as a branch
This commit is contained in:
Junio C Hamano
2020-06-19 14:52:43 -07:00
50 changed files with 4108 additions and 110 deletions

View File

@ -57,6 +57,7 @@ static int advertise_push_options;
static int unpack_limit = 100;
static off_t max_input_size;
static int report_status;
static int report_status_v2;
static int use_sideband;
static int use_atomic;
static int use_push_options;
@ -77,6 +78,7 @@ static struct object_id push_cert_oid;
static struct signature_check sigcheck;
static const char *push_cert_nonce;
static const char *cert_nonce_seed;
static struct string_list proc_receive_refs;
static const char *NONCE_UNSOLICITED = "UNSOLICITED";
static const char *NONCE_BAD = "BAD";
@ -229,6 +231,20 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
return 0;
}
if (strcmp(var, "receive.procreceiverefs") == 0) {
char *prefix;
int len;
if (!value)
return config_error_nonbool(var);
prefix = xstrdup(value);
len = strlen(prefix);
while (len && prefix[len - 1] == '/')
prefix[--len] = '\0';
string_list_append(&proc_receive_refs, prefix);
return 0;
}
return git_default_config(var, value, cb);
}
@ -240,7 +256,7 @@ static void show_ref(const char *path, const struct object_id *oid)
struct strbuf cap = STRBUF_INIT;
strbuf_addstr(&cap,
"report-status delete-refs side-band-64k quiet");
"report-status report-status-v2 delete-refs side-band-64k quiet");
if (advertise_atomic_push)
strbuf_addstr(&cap, " atomic");
if (prefer_ofs_delta)
@ -310,11 +326,14 @@ static void write_head_info(void)
packet_flush(1);
}
#define RUN_PROC_RECEIVE_SCHEDULED 1
#define RUN_PROC_RECEIVE_RETURNED 2
struct command {
struct command *next;
const char *error_string;
struct ref_push_report report;
unsigned int skip_update:1,
did_not_exist:1;
did_not_exist:1,
run_proc_receive:2;
int index;
struct object_id old_oid;
struct object_id new_oid;
@ -773,17 +792,38 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
{
struct receive_hook_feed_state *state = state_;
struct command *cmd = state->cmd;
static struct ref_push_report_options *options = NULL;
while (cmd &&
state->skip_broken && (cmd->error_string || cmd->did_not_exist))
state->skip_broken && (cmd->report.error_message || cmd->did_not_exist))
cmd = cmd->next;
if (!cmd)
return -1; /* EOF */
if (!bufp)
return 0; /* OK, can feed something. */
strbuf_reset(&state->buf);
strbuf_addf(&state->buf, "%s %s %s\n",
oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid),
cmd->ref_name);
state->cmd = cmd->next;
if (!options)
options = cmd->report.options;
if (options) {
struct object_id *old_oid;
struct object_id *new_oid;
const char *ref_name;
old_oid = options->old_oid ? options->old_oid : &cmd->old_oid;
new_oid = options->new_oid ? options->new_oid : &cmd->new_oid;
ref_name = options->ref_name ? options->ref_name : cmd->ref_name;
strbuf_addf(&state->buf, "%s %s %s\n",
oid_to_hex(old_oid), oid_to_hex(new_oid),
ref_name);
options = options->next;
if (!options)
state->cmd = cmd->next;
} else {
strbuf_addf(&state->buf, "%s %s %s\n",
oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid),
cmd->ref_name);
state->cmd = cmd->next;
}
if (bufp) {
*bufp = state->buf.buf;
*sizep = state->buf.len;
@ -840,6 +880,268 @@ static int run_update_hook(struct command *cmd)
return finish_command(&proc);
}
static struct command *find_command_by_refname(const struct command *list,
const char *refname)
{
for (; list; list = list->next)
if (!strcmp(list->ref_name, refname))
return (struct command *)list;
return NULL;
}
static int read_proc_receive_report(struct packet_reader *reader,
struct command *commands,
struct strbuf *errmsg)
{
struct command *cmd;
struct command *hint = NULL;
int code = 0;
int new_options = 1;
for (;;) {
struct object_id old_oid, new_oid;
const char *head;
const char *refname;
char *p;
if (packet_reader_read(reader) != PACKET_READ_NORMAL)
break;
head = reader->line;
p = strchr(head, ' ');
if (!p) {
strbuf_addf(errmsg, "proc-receive reported incomplete status line: '%s'\n", head);
code = -1;
continue;
}
*p++ = '\0';
if (!strcmp(head, "option")) {
struct ref_push_report_options *options;
const char *key, *val;
if (!hint) {
if (new_options) {
strbuf_addstr(errmsg, "proc-receive reported 'option' without a matching 'ok/ng' directive\n");
new_options = 0;
}
code = -1;
continue;
}
options = hint->report.options;
while (options && options->next)
options = options->next;
if (new_options) {
if (!options) {
hint->report.options = xcalloc(1, sizeof(struct ref_push_report_options));
options = hint->report.options;
} else {
options->next = xcalloc(1, sizeof(struct ref_push_report_options));
options = options->next;
}
new_options = 0;
}
assert(options);
key = p;
p = strchr(key, ' ');
if (p)
*p++ = '\0';
val = p;
if (!strcmp(key, "refname"))
options->ref_name = xstrdup_or_null(val);
else if (!strcmp(key, "old-oid") && val &&
!parse_oid_hex(val, &old_oid, &val))
options->old_oid = oiddup(&old_oid);
else if (!strcmp(key, "new-oid") && val &&
!parse_oid_hex(val, &new_oid, &val))
options->new_oid = oiddup(&new_oid);
else if (!strcmp(key, "forced-update"))
options->forced_update = 1;
else if (!strcmp(key, "fall-through"))
/* Fall through, let 'receive-pack' to execute it. */
hint->run_proc_receive = 0;
continue;
}
refname = p;
p = strchr(refname, ' ');
if (p)
*p++ = '\0';
if (strcmp(head, "ok") && strcmp(head, "ng")) {
strbuf_addf(errmsg, "proc-receive reported bad status '%s' on ref '%s'\n",
head, refname);
code = -1;
continue;
}
/* first try searching at our hint, falling back to all refs */
if (hint)
hint = find_command_by_refname(hint, refname);
if (!hint)
hint = find_command_by_refname(commands, refname);
if (!hint) {
strbuf_addf(errmsg, "proc-receive reported status on unknown ref: %s\n",
refname);
code = -1;
continue;
}
if (!hint->run_proc_receive) {
strbuf_addf(errmsg, "proc-receive reported status on unexpected ref: %s\n",
refname);
code = -1;
continue;
}
if (!strcmp(head, "ng")) {
if (p)
hint->report.error_message = xstrdup(p);
else
hint->report.error_message = "failed";
code = -1;
}
if (hint->run_proc_receive)
hint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;
new_options = 1;
}
for (cmd = commands; cmd; cmd = cmd->next)
if (cmd->run_proc_receive && !cmd->report.error_message &&
!(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) {
cmd->report.error_message = "proc-receive failed to report status";
code = -1;
}
return code;
}
static int run_proc_receive_hook(struct command *commands,
const struct string_list *push_options)
{
struct child_process proc = CHILD_PROCESS_INIT;
struct async muxer;
struct command *cmd;
const char *argv[2];
struct packet_reader reader;
struct strbuf cap = STRBUF_INIT;
struct strbuf errmsg = STRBUF_INIT;
int hook_use_push_options = 0;
int version = 0;
int code;
argv[0] = find_hook("proc-receive");
if (!argv[0]) {
rp_error("cannot find hook 'proc-receive'");
return -1;
}
argv[1] = NULL;
proc.argv = argv;
proc.in = -1;
proc.out = -1;
proc.trace2_hook_name = "proc-receive";
if (use_sideband) {
memset(&muxer, 0, sizeof(muxer));
muxer.proc = copy_to_sideband;
muxer.in = -1;
code = start_async(&muxer);
if (code)
return code;
proc.err = muxer.in;
} else {
proc.err = 0;
}
code = start_command(&proc);
if (code) {
if (use_sideband)
finish_async(&muxer);
return code;
}
sigchain_push(SIGPIPE, SIG_IGN);
/* Version negotiaton */
packet_reader_init(&reader, proc.out, NULL, 0,
PACKET_READ_CHOMP_NEWLINE |
PACKET_READ_GENTLE_ON_EOF);
if (use_atomic)
strbuf_addstr(&cap, " atomic");
if (use_push_options)
strbuf_addstr(&cap, " push-options");
if (cap.len) {
packet_write_fmt(proc.in, "version=1%c%s\n", '\0', cap.buf + 1);
strbuf_release(&cap);
} else {
packet_write_fmt(proc.in, "version=1\n");
}
packet_flush(proc.in);
for (;;) {
int linelen;
if (packet_reader_read(&reader) != PACKET_READ_NORMAL)
break;
if (reader.pktlen > 8 && starts_with(reader.line, "version=")) {
version = atoi(reader.line + 8);
linelen = strlen(reader.line);
if (linelen < reader.pktlen) {
const char *feature_list = reader.line + linelen + 1;
if (parse_feature_request(feature_list, "push-options"))
hook_use_push_options = 1;
}
}
}
if (version != 1) {
strbuf_addf(&errmsg, "proc-receive version '%d' is not supported",
version);
code = -1;
goto cleanup;
}
/* Send commands */
for (cmd = commands; cmd; cmd = cmd->next) {
if (!cmd->run_proc_receive || cmd->skip_update || cmd->report.error_message)
continue;
packet_write_fmt(proc.in, "%s %s %s",
oid_to_hex(&cmd->old_oid),
oid_to_hex(&cmd->new_oid),
cmd->ref_name);
}
packet_flush(proc.in);
/* Send push options */
if (hook_use_push_options) {
struct string_list_item *item;
for_each_string_list_item(item, push_options)
packet_write_fmt(proc.in, "%s", item->string);
packet_flush(proc.in);
}
/* Read result from proc-receive */
code = read_proc_receive_report(&reader, commands, &errmsg);
cleanup:
close(proc.in);
close(proc.out);
if (use_sideband)
finish_async(&muxer);
if (finish_command(&proc))
code = -1;
if (errmsg.len >0) {
char *p = errmsg.buf;
p += errmsg.len - 1;
if (*p == '\n')
*p = '\0';
rp_error("%s", errmsg.buf);
strbuf_release(&errmsg);
}
sigchain_pop(SIGPIPE);
return code;
}
static char *refuse_unconfigured_deny_msg =
N_("By default, updating the current branch in a non-bare repository\n"
"is denied, because it will make the index and work tree inconsistent\n"
@ -1203,7 +1505,7 @@ static void run_update_post_hook(struct command *commands)
return;
for (cmd = commands; cmd; cmd = cmd->next) {
if (cmd->error_string || cmd->did_not_exist)
if (cmd->report.error_message || cmd->did_not_exist)
continue;
if (!proc.args.argc)
argv_array_push(&proc.args, hook);
@ -1237,7 +1539,7 @@ static void check_aliased_update_internal(struct command *cmd,
if (!dst_name) {
rp_error("refusing update to broken symref '%s'", cmd->ref_name);
cmd->skip_update = 1;
cmd->error_string = "broken symref";
cmd->report.error_message = "broken symref";
return;
}
dst_name = strip_namespace(dst_name);
@ -1264,7 +1566,7 @@ static void check_aliased_update_internal(struct command *cmd,
find_unique_abbrev(&dst_cmd->old_oid, DEFAULT_ABBREV),
find_unique_abbrev(&dst_cmd->new_oid, DEFAULT_ABBREV));
cmd->error_string = dst_cmd->error_string =
cmd->report.error_message = dst_cmd->report.error_message =
"inconsistent aliased update";
}
@ -1293,7 +1595,7 @@ static void check_aliased_updates(struct command *commands)
string_list_sort(&ref_list);
for (cmd = commands; cmd; cmd = cmd->next) {
if (!cmd->error_string)
if (!cmd->report.error_message)
check_aliased_update(cmd, &ref_list);
}
@ -1330,7 +1632,7 @@ static void set_connectivity_errors(struct command *commands,
&opt))
continue;
cmd->error_string = "missing necessary objects";
cmd->report.error_message = "missing necessary objects";
}
}
@ -1369,7 +1671,7 @@ static void reject_updates_to_hidden(struct command *commands)
prefix_len = refname_full.len;
for (cmd = commands; cmd; cmd = cmd->next) {
if (cmd->error_string)
if (cmd->report.error_message)
continue;
strbuf_setlen(&refname_full, prefix_len);
@ -1378,9 +1680,9 @@ static void reject_updates_to_hidden(struct command *commands)
if (!ref_is_hidden(cmd->ref_name, refname_full.buf))
continue;
if (is_null_oid(&cmd->new_oid))
cmd->error_string = "deny deleting a hidden ref";
cmd->report.error_message = "deny deleting a hidden ref";
else
cmd->error_string = "deny updating a hidden ref";
cmd->report.error_message = "deny updating a hidden ref";
}
strbuf_release(&refname_full);
@ -1388,7 +1690,7 @@ static void reject_updates_to_hidden(struct command *commands)
static int should_process_cmd(struct command *cmd)
{
return !cmd->error_string && !cmd->skip_update;
return !cmd->report.error_message && !cmd->skip_update;
}
static void warn_if_skipped_connectivity_check(struct command *commands,
@ -1415,24 +1717,24 @@ static void execute_commands_non_atomic(struct command *commands,
struct strbuf err = STRBUF_INIT;
for (cmd = commands; cmd; cmd = cmd->next) {
if (!should_process_cmd(cmd))
if (!should_process_cmd(cmd) || cmd->run_proc_receive)
continue;
transaction = ref_transaction_begin(&err);
if (!transaction) {
rp_error("%s", err.buf);
strbuf_reset(&err);
cmd->error_string = "transaction failed to start";
cmd->report.error_message = "transaction failed to start";
continue;
}
cmd->error_string = update(cmd, si);
cmd->report.error_message = update(cmd, si);
if (!cmd->error_string
if (!cmd->report.error_message
&& ref_transaction_commit(transaction, &err)) {
rp_error("%s", err.buf);
strbuf_reset(&err);
cmd->error_string = "failed to update ref";
cmd->report.error_message = "failed to update ref";
}
ref_transaction_free(transaction);
}
@ -1455,12 +1757,12 @@ static void execute_commands_atomic(struct command *commands,
}
for (cmd = commands; cmd; cmd = cmd->next) {
if (!should_process_cmd(cmd))
if (!should_process_cmd(cmd) || cmd->run_proc_receive)
continue;
cmd->error_string = update(cmd, si);
cmd->report.error_message = update(cmd, si);
if (cmd->error_string)
if (cmd->report.error_message)
goto failure;
}
@ -1473,8 +1775,8 @@ static void execute_commands_atomic(struct command *commands,
failure:
for (cmd = commands; cmd; cmd = cmd->next)
if (!cmd->error_string)
cmd->error_string = reported_error;
if (!cmd->report.error_message)
cmd->report.error_message = reported_error;
cleanup:
ref_transaction_free(transaction);
@ -1491,10 +1793,11 @@ static void execute_commands(struct command *commands,
struct iterate_data data;
struct async muxer;
int err_fd = 0;
int run_proc_receive = 0;
if (unpacker_error) {
for (cmd = commands; cmd; cmd = cmd->next)
cmd->error_string = "unpacker error";
cmd->report.error_message = "unpacker error";
return;
}
@ -1520,10 +1823,36 @@ static void execute_commands(struct command *commands,
reject_updates_to_hidden(commands);
/*
* Try to find commands that have special prefix in their reference names,
* and mark them to run an external "proc-receive" hook later.
*/
if (proc_receive_refs.nr > 0) {
struct strbuf refname_full = STRBUF_INIT;
size_t prefix_len;
strbuf_addstr(&refname_full, get_git_namespace());
prefix_len = refname_full.len;
for (cmd = commands; cmd; cmd = cmd->next) {
if (!should_process_cmd(cmd))
continue;
strbuf_setlen(&refname_full, prefix_len);
strbuf_addstr(&refname_full, cmd->ref_name);
if (ref_matches(&proc_receive_refs, cmd->ref_name, refname_full.buf)) {
cmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;
run_proc_receive = 1;
}
}
strbuf_release(&refname_full);
}
if (run_receive_hook(commands, "pre-receive", 0, push_options)) {
for (cmd = commands; cmd; cmd = cmd->next) {
if (!cmd->error_string)
cmd->error_string = "pre-receive hook declined";
if (!cmd->report.error_message)
cmd->report.error_message = "pre-receive hook declined";
}
return;
}
@ -1534,8 +1863,8 @@ static void execute_commands(struct command *commands,
*/
if (tmp_objdir_migrate(tmp_objdir) < 0) {
for (cmd = commands; cmd; cmd = cmd->next) {
if (!cmd->error_string)
cmd->error_string = "unable to migrate objects to permanent storage";
if (!cmd->report.error_message)
cmd->report.error_message = "unable to migrate objects to permanent storage";
}
return;
}
@ -1546,6 +1875,14 @@ static void execute_commands(struct command *commands,
free(head_name_to_free);
head_name = head_name_to_free = resolve_refdup("HEAD", 0, NULL, NULL);
if (run_proc_receive &&
run_proc_receive_hook(commands, push_options))
for (cmd = commands; cmd; cmd = cmd->next)
if (!cmd->report.error_message &&
!(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED) &&
(cmd->run_proc_receive || use_atomic))
cmd->report.error_message = "fail to run proc-receive hook";
if (use_atomic)
execute_commands_atomic(commands, si);
else
@ -1629,6 +1966,8 @@ static struct command *read_head_info(struct packet_reader *reader,
int len = 0;
if (parse_feature_request(feature_list, "report-status"))
report_status = 1;
if (parse_feature_request(feature_list, "report-status-v2"))
report_status_v2 = 1;
if (parse_feature_request(feature_list, "side-band-64k"))
use_sideband = LARGE_PACKET_MAX;
if (parse_feature_request(feature_list, "quiet"))
@ -1916,7 +2255,7 @@ static void update_shallow_info(struct command *commands,
if (is_null_oid(&cmd->new_oid))
continue;
if (ref_status[cmd->index]) {
cmd->error_string = "shallow update not allowed";
cmd->report.error_message = "shallow update not allowed";
cmd->skip_update = 1;
}
}
@ -1931,12 +2270,55 @@ static void report(struct command *commands, const char *unpack_status)
packet_buf_write(&buf, "unpack %s\n",
unpack_status ? unpack_status : "ok");
for (cmd = commands; cmd; cmd = cmd->next) {
if (!cmd->error_string)
if (!cmd->report.error_message)
packet_buf_write(&buf, "ok %s\n",
cmd->ref_name);
else
packet_buf_write(&buf, "ng %s %s\n",
cmd->ref_name, cmd->error_string);
cmd->ref_name, cmd->report.error_message);
}
packet_buf_flush(&buf);
if (use_sideband)
send_sideband(1, 1, buf.buf, buf.len, use_sideband);
else
write_or_die(1, buf.buf, buf.len);
strbuf_release(&buf);
}
static void report_v2(struct command *commands, const char *unpack_status)
{
struct command *cmd;
struct strbuf buf = STRBUF_INIT;
struct ref_push_report_options *options;
packet_buf_write(&buf, "unpack %s\n",
unpack_status ? unpack_status : "ok");
for (cmd = commands; cmd; cmd = cmd->next) {
int count = 0;
if (!cmd->report.error_message)
packet_buf_write(&buf, "ok %s\n",
cmd->ref_name);
else
packet_buf_write(&buf, "ng %s %s\n",
cmd->ref_name,
cmd->report.error_message);
for (options = cmd->report.options; options; options = options->next) {
if (count++ > 0)
packet_buf_write(&buf, "ok %s\n", cmd->ref_name);
if (options->ref_name)
packet_buf_write(&buf, "option refname %s\n",
options->ref_name);
if (options->old_oid)
packet_buf_write(&buf, "option old-oid %s\n",
oid_to_hex(options->old_oid));
if (options->new_oid)
packet_buf_write(&buf, "option new-oid %s\n",
oid_to_hex(options->new_oid));
if (options->forced_update)
packet_buf_write(&buf, "option forced-update\n");
}
}
packet_buf_flush(&buf);
@ -1974,6 +2356,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
OPT_END()
};
string_list_init(&proc_receive_refs, 0);
packet_trace_identity("receive-pack");
argc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0);
@ -2040,7 +2424,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
if (!check_cert_push_options(&push_options)) {
struct command *cmd;
for (cmd = commands; cmd; cmd = cmd->next)
cmd->error_string = "inconsistent push options";
cmd->report.error_message = "inconsistent push options";
}
prepare_shallow_info(&si, &shallow);
@ -2055,7 +2439,9 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
&push_options);
if (pack_lockfile)
unlink_or_warn(pack_lockfile);
if (report_status)
if (report_status_v2)
report_v2(commands, unpack_status);
else if (report_status)
report(commands, unpack_status);
run_receive_hook(commands, "post-receive", 1,
&push_options);
@ -2089,5 +2475,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
oid_array_clear(&shallow);
oid_array_clear(&ref);
free((void *)push_cert_nonce);
string_list_clear(&proc_receive_refs, 0);
return 0;
}