Merge branch 'jx/proc-receive-hook'

"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
  doc: add document for capability report-status-v2
  New capability "report-status-v2" for git-push
  receive-pack: feed report options to post-receive
  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-09-25 15:25:39 -07:00
48 changed files with 4328 additions and 103 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;
@ -97,6 +98,17 @@ static int keepalive_in_sec = 5;
static struct tmp_objdir *tmp_objdir;
static struct proc_receive_ref {
unsigned int want_add:1,
want_delete:1,
want_modify:1,
negative_ref:1;
char *ref_prefix;
struct proc_receive_ref *next;
} *proc_receive_ref;
static void proc_receive_ref_append(const char *prefix);
static enum deny_action parse_deny_action(const char *var, const char *value)
{
if (value) {
@ -229,6 +241,13 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
return 0;
}
if (strcmp(var, "receive.procreceiverefs") == 0) {
if (!value)
return config_error_nonbool(var);
proc_receive_ref_append(value);
return 0;
}
return git_default_config(var, value, cb);
}
@ -240,7 +259,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,17 +329,94 @@ 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;
char ref_name[FLEX_ARRAY]; /* more */
};
static void proc_receive_ref_append(const char *prefix)
{
struct proc_receive_ref *ref_pattern;
char *p;
int len;
ref_pattern = xcalloc(1, sizeof(struct proc_receive_ref));
p = strchr(prefix, ':');
if (p) {
while (prefix < p) {
if (*prefix == 'a')
ref_pattern->want_add = 1;
else if (*prefix == 'd')
ref_pattern->want_delete = 1;
else if (*prefix == 'm')
ref_pattern->want_modify = 1;
else if (*prefix == '!')
ref_pattern->negative_ref = 1;
prefix++;
}
prefix++;
} else {
ref_pattern->want_add = 1;
ref_pattern->want_delete = 1;
ref_pattern->want_modify = 1;
}
len = strlen(prefix);
while (len && prefix[len - 1] == '/')
len--;
ref_pattern->ref_prefix = xmemdupz(prefix, len);
if (!proc_receive_ref) {
proc_receive_ref = ref_pattern;
} else {
struct proc_receive_ref *end;
end = proc_receive_ref;
while (end->next)
end = end->next;
end->next = ref_pattern;
}
}
static int proc_receive_ref_matches(struct command *cmd)
{
struct proc_receive_ref *p;
if (!proc_receive_ref)
return 0;
for (p = proc_receive_ref; p; p = p->next) {
const char *match = p->ref_prefix;
const char *remains;
if (!p->want_add && is_null_oid(&cmd->old_oid))
continue;
else if (!p->want_delete && is_null_oid(&cmd->new_oid))
continue;
else if (!p->want_modify &&
!is_null_oid(&cmd->old_oid) &&
!is_null_oid(&cmd->new_oid))
continue;
if (skip_prefix(cmd->ref_name, match, &remains) &&
(!*remains || *remains == '/')) {
if (!p->negative_ref)
return 1;
} else if (p->negative_ref) {
return 1;
}
}
return 0;
}
static void rp_error(const char *err, ...) __attribute__((format (printf, 1, 2)));
static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2)));
@ -692,6 +788,7 @@ static void prepare_push_cert_sha1(struct child_process *proc)
struct receive_hook_feed_state {
struct command *cmd;
struct ref_push_report *report;
int skip_broken;
struct strbuf buf;
const struct string_list *push_options;
@ -779,11 +876,31 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
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 (!state->report)
state->report = cmd->report;
if (state->report) {
struct object_id *old_oid;
struct object_id *new_oid;
const char *ref_name;
old_oid = state->report->old_oid ? state->report->old_oid : &cmd->old_oid;
new_oid = state->report->new_oid ? state->report->new_oid : &cmd->new_oid;
ref_name = state->report->ref_name ? state->report->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);
state->report = state->report->next;
if (!state->report)
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;
@ -802,6 +919,7 @@ static int run_receive_hook(struct command *commands,
strbuf_init(&state.buf, 0);
state.cmd = commands;
state.skip_broken = skip_broken;
state.report = NULL;
if (feed_receive_hook(&state, NULL, NULL))
return 0;
state.cmd = commands;
@ -840,6 +958,268 @@ static int run_update_hook(struct command *cmd)
return finish_command(&proc);
}
static struct command *find_command_by_refname(struct command *list,
const char *refname)
{
for (; list; list = list->next)
if (!strcmp(list->ref_name, refname))
return 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;
struct ref_push_report *report = NULL;
int new_report = 0;
int code = 0;
int once = 0;
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")) {
const char *key, *val;
if (!hint || !(report || new_report)) {
if (!once++)
strbuf_addstr(errmsg, "proc-receive reported 'option' without a matching 'ok/ng' directive\n");
code = -1;
continue;
}
if (new_report) {
if (!hint->report) {
hint->report = xcalloc(1, sizeof(struct ref_push_report));
report = hint->report;
} else {
report = hint->report;
while (report->next)
report = report->next;
report->next = xcalloc(1, sizeof(struct ref_push_report));
report = report->next;
}
new_report = 0;
}
key = p;
p = strchr(key, ' ');
if (p)
*p++ = '\0';
val = p;
if (!strcmp(key, "refname"))
report->ref_name = xstrdup_or_null(val);
else if (!strcmp(key, "old-oid") && val &&
!parse_oid_hex(val, &old_oid, &val))
report->old_oid = oiddup(&old_oid);
else if (!strcmp(key, "new-oid") && val &&
!parse_oid_hex(val, &new_oid, &val))
report->new_oid = oiddup(&new_oid);
else if (!strcmp(key, "forced-update"))
report->forced_update = 1;
else if (!strcmp(key, "fall-through"))
/* Fall through, let 'receive-pack' to execute it. */
hint->run_proc_receive = 0;
continue;
}
report = NULL;
new_report = 0;
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;
}
hint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;
if (!strcmp(head, "ng")) {
if (p)
hint->error_string = xstrdup(p);
else
hint->error_string = "failed";
code = -1;
continue;
}
new_report = 1;
}
for (cmd = commands; cmd; cmd = cmd->next)
if (cmd->run_proc_receive && !cmd->error_string &&
!(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) {
cmd->error_string = "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->error_string)
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"
@ -1415,7 +1795,7 @@ 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);
@ -1455,7 +1835,7 @@ 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);
@ -1491,6 +1871,7 @@ 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)
@ -1520,6 +1901,22 @@ 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_ref) {
for (cmd = commands; cmd; cmd = cmd->next) {
if (!should_process_cmd(cmd))
continue;
if (proc_receive_ref_matches(cmd)) {
cmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED;
run_proc_receive = 1;
}
}
}
if (run_receive_hook(commands, "pre-receive", 0, push_options)) {
for (cmd = commands; cmd; cmd = cmd->next) {
if (!cmd->error_string)
@ -1546,6 +1943,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->error_string &&
!(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED) &&
(cmd->run_proc_receive || use_atomic))
cmd->error_string = "fail to run proc-receive hook";
if (use_atomic)
execute_commands_atomic(commands, si);
else
@ -1629,6 +2034,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"))
@ -1947,6 +2354,51 @@ static void report(struct command *commands, const char *unpack_status)
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 *report;
packet_buf_write(&buf, "unpack %s\n",
unpack_status ? unpack_status : "ok");
for (cmd = commands; cmd; cmd = cmd->next) {
int count = 0;
if (cmd->error_string) {
packet_buf_write(&buf, "ng %s %s\n",
cmd->ref_name,
cmd->error_string);
continue;
}
packet_buf_write(&buf, "ok %s\n",
cmd->ref_name);
for (report = cmd->report; report; report = report->next) {
if (count++ > 0)
packet_buf_write(&buf, "ok %s\n",
cmd->ref_name);
if (report->ref_name)
packet_buf_write(&buf, "option refname %s\n",
report->ref_name);
if (report->old_oid)
packet_buf_write(&buf, "option old-oid %s\n",
oid_to_hex(report->old_oid));
if (report->new_oid)
packet_buf_write(&buf, "option new-oid %s\n",
oid_to_hex(report->new_oid));
if (report->forced_update)
packet_buf_write(&buf, "option forced-update\n");
}
}
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 int delete_only(struct command *commands)
{
struct command *cmd;
@ -2055,7 +2507,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);