@@ -725,6 +725,7 @@ TEST_BUILTINS_OBJS += test-parse-pathspec-file.o
TEST_BUILTINS_OBJS += test-path-utils.o
TEST_BUILTINS_OBJS += test-pkt-line.o
TEST_BUILTINS_OBJS += test-prio-queue.o
+TEST_BUILTINS_OBJS += test-proc-receive.o
TEST_BUILTINS_OBJS += test-progress.o
TEST_BUILTINS_OBJS += test-reach.o
TEST_BUILTINS_OBJS += test-read-cache.o
@@ -308,11 +308,14 @@ static void write_head_info(void)
packet_flush(1);
}
+#define RUN_PROC_RECEIVE_SCHEDULE 1
+#define RUN_PROC_RECEIVE_RETURNED 2
struct command {
struct command *next;
const char *error_string;
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;
@@ -817,6 +820,212 @@ 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_result(struct packet_reader *reader,
+ struct command *commands)
+{
+ struct command *hint;
+ struct command *cmd;
+ int code = 0;
+
+ hint = NULL;
+ for (;;) {
+ struct object_id old_oid, new_oid;
+ const char *refname;
+ const char *p;
+ char *status;
+ char *msg = NULL;
+
+ if (packet_reader_read(reader) != PACKET_READ_NORMAL)
+ break;
+ if (parse_oid_hex(reader->line, &old_oid, &p) ||
+ *p++ != ' ' ||
+ parse_oid_hex(p, &new_oid, &p) ||
+ *p++ != ' ')
+ die("protocol error: proc-receive expected 'old new ref status [msg]', got '%s'",
+ reader->line);
+
+ refname = p;
+ status = strchr(p, ' ');
+ if (!status)
+ die("protocol error: proc-receive expected 'old new ref status [msg]', got '%s'",
+ reader->line);
+ *status++ = '\0';
+ if (strlen(status) > 2 && *(status + 2) == ' ') {
+ msg = status + 2;
+ *msg++ = '\0';
+ }
+ if (strlen(status) != 2)
+ die("protocol error: proc-receive has bad status '%s' for '%s'",
+ status, reader->line);
+
+ /* 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) {
+ warning("proc-receive reported status on unknown ref: %s",
+ refname);
+ continue;
+ }
+ if (!hint->run_proc_receive) {
+ warning("proc-receive reported status on ref of builtin command: %s",
+ refname);
+ continue;
+ }
+ hint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED;
+ oidcpy(&hint->old_oid, &old_oid);
+ oidcpy(&hint->new_oid, &new_oid);
+ if (!strcmp(status, "ng")) {
+ if (msg)
+ hint->error_string = xstrdup(msg);
+ else
+ hint->error_string = "failed";
+ code = 1;
+ } else if (strcmp("ok", status)) {
+ die("protocol error: proc-receive has bad status '%s' for '%s'",
+ status, reader->line);
+ }
+ }
+
+ for (cmd = commands; cmd; cmd = cmd->next)
+ if (cmd->run_proc_receive &&
+ !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED))
+ cmd->error_string = "no report from proc-receive";
+
+ 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;
+ int pr_use_push_options = 0;
+ int version = 0;
+ int code;
+
+ argv[0] = find_hook("proc-receive");
+ if (!argv[0]) {
+ rp_error("cannot to 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_DIE_ON_ERR_PACKET);
+ 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"))
+ pr_use_push_options = 1;
+ }
+ }
+ }
+
+ if (version != 1)
+ die("protocol error: unknown proc-receive version '%d'", version);
+
+ /* Send commands */
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ char *old_hex, *new_hex;
+
+ if (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string)
+ continue;
+
+ old_hex = oid_to_hex(&cmd->old_oid);
+ new_hex = oid_to_hex(&cmd->new_oid);
+
+ packet_write_fmt(proc.in, "%s %s %s",
+ old_hex, new_hex, cmd->ref_name);
+ }
+ packet_flush(proc.in);
+
+ /* Send push options */
+ if (pr_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_result(&reader, commands);
+ close(proc.in);
+ close(proc.out);
+ if (use_sideband)
+ finish_async(&muxer);
+ if (finish_command(&proc))
+ die("proc-receive did not exit properly");
+
+ 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"
@@ -1392,7 +1601,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);
@@ -1432,7 +1641,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);
@@ -1468,6 +1677,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)
@@ -1497,6 +1707,20 @@ 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.
+ */
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ if (!should_process_cmd(cmd))
+ continue;
+
+ /* TODO: replace the fixed prefix by looking up git config variables. */
+ if (!strncmp(cmd->ref_name, "refs/for/", 9)) {
+ cmd->run_proc_receive = 1;
+ 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)
@@ -1523,6 +1747,18 @@ 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) {
+ int code;
+
+ code = run_proc_receive_hook(commands, push_options);
+ if (code) {
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ if (!cmd->error_string && (cmd->run_proc_receive || use_atomic))
+ cmd->error_string = "fail to run proc-receive hook";
+ }
+ }
+ }
+
if (use_atomic)
execute_commands_atomic(commands, si);
else
new file mode 100644
@@ -0,0 +1,172 @@
+#include "cache.h"
+#include "connect.h"
+#include "parse-options.h"
+#include "pkt-line.h"
+#include "string-list.h"
+#include "test-tool.h"
+
+static const char *proc_receive_usage[] = {
+ "test-tool proc-receive [<options>...]",
+ NULL
+};
+
+static int version = 1;
+static int verbose = 0;
+static int no_push_options = 0;
+static int use_atomic = 0;
+static int use_push_options = 0;
+static struct string_list returns = STRING_LIST_INIT_NODUP;
+
+struct command {
+ struct command *next;
+ const char *error_string;
+ unsigned int skip_update:1,
+ did_not_exist:1;
+ int index;
+ struct object_id old_oid;
+ struct object_id new_oid;
+ char ref_name[FLEX_ARRAY]; /* more */
+};
+
+static void proc_receive_verison(struct packet_reader *reader) {
+ int server_version = 0;
+
+ for (;;) {
+ int linelen;
+
+ if (packet_reader_read(reader) != PACKET_READ_NORMAL)
+ break;
+
+ if (reader->pktlen > 8 && starts_with(reader->line, "version=")) {
+ server_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, "atomic"))
+ use_atomic= 1;
+ if (parse_feature_request(feature_list, "push-options"))
+ use_push_options = 1;
+ }
+ }
+ }
+
+ if (server_version != 1)
+ die("bad protocol version: %d", server_version);
+
+ packet_write_fmt(1, "version=%d%c%s\n",
+ version, '\0',
+ use_push_options && !no_push_options ? "push-options": "");
+ packet_flush(1);
+}
+
+static void proc_receive_read_commands(struct packet_reader *reader,
+ struct command **commands)
+{
+ struct command **tail = commands;
+
+ for (;;) {
+ struct object_id old_oid, new_oid;
+ struct command *cmd;
+ const char *refname;
+ const char *p;
+
+ if (packet_reader_read(reader) != PACKET_READ_NORMAL) {
+ break;
+ }
+
+ if (parse_oid_hex(reader->line, &old_oid, &p) ||
+ *p++ != ' ' ||
+ parse_oid_hex(p, &new_oid, &p) ||
+ *p++ != ' ')
+ die("protocol error: expected 'old new ref', got '%s'",
+ reader->line);
+ refname = p;
+ FLEX_ALLOC_MEM(cmd, ref_name, refname, strlen(refname));
+ oidcpy(&cmd->old_oid, &old_oid);
+ oidcpy(&cmd->new_oid, &new_oid);
+
+ *tail = cmd;
+ tail = &cmd->next;
+ }
+}
+
+static void proc_receive_read_push_options(struct packet_reader *reader,
+ struct string_list *options)
+{
+
+ if (no_push_options || !use_push_options)
+ return;
+
+ while (1) {
+ if (packet_reader_read(reader) != PACKET_READ_NORMAL)
+ break;
+
+ string_list_append(options, reader->line);
+ }
+}
+
+int cmd__proc_receive(int argc, const char **argv)
+{
+ struct packet_reader reader;
+ struct command *commands;
+ struct string_list push_options = STRING_LIST_INIT_DUP;
+ struct string_list_item *item;
+ struct option options[] = {
+ OPT_BOOL(0, "no-push-options", &no_push_options,
+ "disable push options"),
+ OPT_STRING_LIST('r', "return", &returns, "old/new/ref/status/msg",
+ "return of results"),
+ OPT__VERBOSE(&verbose, "be verbose"),
+ OPT_INTEGER('V', "version", &version,
+ "use this protocol version number"),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, "test-tools", options, proc_receive_usage, 0);
+ if (argc > 0)
+ usage_msg_opt("Too many arguments.", proc_receive_usage, options);
+
+ packet_reader_init(&reader, 0, NULL, 0,
+ PACKET_READ_CHOMP_NEWLINE |
+ PACKET_READ_DIE_ON_ERR_PACKET);
+
+ proc_receive_verison(&reader);
+ proc_receive_read_commands(&reader, &commands);
+ proc_receive_read_push_options(&reader, &push_options);
+
+ if (verbose) {
+ struct command *cmd;
+
+ if (use_push_options || use_atomic)
+ fprintf(stderr, "proc-receive:%s%s\n",
+ use_atomic? " atomic": "",
+ use_push_options ? " push_options": "");
+
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ char *old_hex, *new_hex;
+
+ old_hex = oid_to_hex(&cmd->old_oid);
+ new_hex = oid_to_hex(&cmd->new_oid);
+ fprintf(stderr, "proc-receive< %s %s %s\n",
+ old_hex, new_hex, cmd->ref_name);
+ }
+
+ if (push_options.nr > 0) {
+ for_each_string_list_item(item, &push_options)
+ fprintf(stderr, "proc-receive< %s\n", item->string);
+ }
+
+ if (returns.nr) {
+ for_each_string_list_item(item, &returns)
+ fprintf(stderr, "proc-receive> %s\n", item->string);
+ }
+ }
+
+ if (returns.nr) {
+ for_each_string_list_item(item, &returns)
+ packet_write_fmt(1, "%s\n", item->string);
+ }
+ packet_flush(1);
+
+ return 0;
+}
@@ -43,6 +43,7 @@ static struct test_cmd cmds[] = {
{ "path-utils", cmd__path_utils },
{ "pkt-line", cmd__pkt_line },
{ "prio-queue", cmd__prio_queue },
+ { "proc-receive", cmd__proc_receive},
{ "progress", cmd__progress },
{ "reach", cmd__reach },
{ "read-cache", cmd__read_cache },
@@ -33,6 +33,7 @@ int cmd__parse_pathspec_file(int argc, const char** argv);
int cmd__path_utils(int argc, const char **argv);
int cmd__pkt_line(int argc, const char **argv);
int cmd__prio_queue(int argc, const char **argv);
+int cmd__proc_receive(int argc, const char **argv);
int cmd__progress(int argc, const char **argv);
int cmd__reach(int argc, const char **argv);
int cmd__read_cache(int argc, const char **argv);
@@ -57,6 +57,24 @@ make_user_friendly_and_stable_output () {
-e "s/[0-9a-f]\{7,\}/<OID>/g"
}
+# Asynchronous sideband may generate inconsistent output messages,
+# sort before comparison.
+test_sorted_cmp () {
+ if ! $GIT_TEST_CMP "$@" >/dev/null 2>&1
+ then
+ cmd=$GIT_TEST_CMP
+ for f in "$@"
+ do
+ sort "$f" >"$f.sorted"
+ cmd="$cmd \"$f.sorted\""
+ done
+ if ! eval $cmd >/dev/null 2>&1
+ then
+ $GIT_TEST_CMP "$@"
+ fi
+ fi
+}
+
# Refs of upstream : master(B) next(A)
# Refs of workbench: master(A) tags/v123
test_expect_success "setup" '
@@ -150,4 +168,541 @@ test_expect_success "normal git-push command" '
test_cmp expect actual
'
+# Refs of upstream : master(A) tags/v123 refs/review/master/topic(A) a/b/c(A)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup" '
+ (
+ cd upstream &&
+ git update-ref -d refs/review/master/topic &&
+ git update-ref -d refs/tags/v123 &&
+ git update-ref -d refs/heads/a/b/c
+ )
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : next(A) refs/for/master/topic(A)
+test_expect_success "no proc-receive hook, fail to push special ref" '
+ test_must_fail git -C workbench push origin \
+ HEAD:next \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: error: cannot to find hook "proc-receive"
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ To ../upstream
+ * [new branch] HEAD -> next
+ ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)
+ error: failed to push some refs to "../upstream"
+ EOF
+ test_cmp expect actual &&
+ git -C upstream show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ <COMMIT-A> refs/heads/next
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(A) next(A)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup" '
+ git -C upstream update-ref -d refs/heads/next
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push --atomic: next(A) refs/for/master/topic(A)
+test_expect_failure "no proc-receive hook, fail all for atomic push" '
+ test_must_fail git -C workbench push --atomic origin \
+ HEAD:next \
+ HEAD:refs/for/master/topic >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: error: cannot to find hook "proc-receive"
+ To ../upstream
+ ! [remote rejected] HEAD -> next (fail to run proc-receive hook)
+ ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)
+ error: failed to push some refs to "../upstream"
+ EOF
+ test_cmp expect actual &&
+ git -C upstream show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (bad version)" '
+ cat >upstream/hooks/proc-receive <<-EOF &&
+ #!/bin/sh
+
+ printf >&2 "# proc-receive hook\n"
+
+ test-tool proc-receive -v --version 2
+ EOF
+ chmod a+x upstream/hooks/proc-receive
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic(A)
+test_expect_success "proc-receive bad protocol: unknown version" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out | grep "protocol error" >actual &&
+ cat >expect <<-EOF &&
+ fatal: protocol error: unknown proc-receive version "2"
+ EOF
+ test_cmp expect actual &&
+ git -C upstream show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (no report)" '
+ cat >upstream/hooks/proc-receive <<-EOF
+ #!/bin/sh
+
+ printf >&2 "# proc-receive hook\n"
+
+ test-tool proc-receive -v
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : next(A) refs/for/master/topic(A)
+test_expect_success "proc-receive bad protocol: no report" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/heads/next \
+ HEAD:refs/for/master/topic >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ To ../upstream
+ * [new branch] HEAD -> next
+ ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)
+ error: failed to push some refs to "../upstream"
+ EOF
+ test_cmp expect actual &&
+ git -C upstream show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ <COMMIT-A> refs/heads/next
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(A) next(A)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup" '
+ git -C upstream update-ref -d refs/heads/next
+
+'
+
+test_expect_success "setup proc-receive hook (bad oid)" '
+ cat >upstream/hooks/proc-receive <<-EOF
+ #!/bin/sh
+
+ printf >&2 "# proc-receive hook\n"
+
+ test-tool proc-receive -v \
+ -r "bad-id new-id ref ok"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic
+test_expect_success "proc-receive bad protocol: bad oid" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/master/topic\
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out | grep "protocol error" >actual &&
+ cat >expect <<-EOF &&
+ fatal: protocol error: proc-receive expected "old new ref status [msg]", got "bad-id new-id ref ok"
+ EOF
+ test_cmp expect actual &&
+ git -C upstream show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (no status)" '
+ cat >upstream/hooks/proc-receive <<-EOF
+ #!/bin/sh
+
+ printf >&2 "# proc-receive hook\n"
+
+ test-tool proc-receive -v \
+ -r "$ZERO_OID $A refs/for/master/topic"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic
+test_expect_success "proc-receive bad protocol: no status" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out | grep "protocol error" >actual &&
+ cat >expect <<-EOF &&
+ fatal: protocol error: proc-receive expected "old new ref status [msg]", got "<ZERO-OID> <COMMIT-A> refs/for/master/topic"
+ EOF
+ test_cmp expect actual &&
+ git -C upstream show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (unknown status)" '
+ cat >upstream/hooks/proc-receive <<-EOF
+ #!/bin/sh
+
+ printf >&2 "# proc-receive hook\n"
+
+ test-tool proc-receive -v \
+ -r "$ZERO_OID $A refs/for/master/topic xx msg"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic
+test_expect_success "proc-receive bad protocol: unknown status" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out | grep "protocol error" >actual &&
+ cat >expect <<-EOF &&
+ fatal: protocol error: proc-receive has bad status "xx" for "<ZERO-OID> <COMMIT-A> refs/for/master/topic"
+ EOF
+ test_cmp expect actual &&
+ git -C upstream show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (bad status)" '
+ cat >upstream/hooks/proc-receive <<-EOF
+ #!/bin/sh
+
+ printf >&2 "# proc-receive hook\n"
+
+ test-tool proc-receive -v \
+ -r "$ZERO_OID $A refs/for/master/topic bad status"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic
+test_expect_success "proc-receive bad protocol: bad status" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out | grep "protocol error" >actual &&
+ cat >expect <<-EOF &&
+ fatal: protocol error: proc-receive has bad status "bad status" for "<ZERO-OID> <COMMIT-A> refs/for/master/topic"
+ EOF
+ test_cmp expect actual &&
+ git -C upstream show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (ng)" '
+ cat >upstream/hooks/proc-receive <<-EOF
+ #!/bin/sh
+
+ printf >&2 "# proc-receive hook\n"
+
+ test-tool proc-receive -v \
+ -r "$ZERO_OID $A refs/for/master/topic ng"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic
+test_expect_success "proc-receive: fail to update (no message)" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng
+ To ../upstream
+ ! [remote rejected] HEAD -> refs/for/master/topic (failed)
+ error: failed to push some refs to "../upstream"
+ EOF
+ test_cmp expect actual &&
+ git -C upstream show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (ng message)" '
+ cat >upstream/hooks/proc-receive <<-EOF
+ #!/bin/sh
+
+ printf >&2 "# proc-receive hook\n"
+
+ test-tool proc-receive -v \
+ -r "$ZERO_OID $A refs/for/master/topic ng error msg"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic
+test_expect_success "proc-receive: fail to update (has message)" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ng error msg
+ To ../upstream
+ ! [remote rejected] HEAD -> refs/for/master/topic (error msg)
+ error: failed to push some refs to "../upstream"
+ EOF
+ test_cmp expect actual &&
+ git -C upstream show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (report status on builtin command)" '
+ cat >upstream/hooks/proc-receive <<-EOF
+ #!/bin/sh
+
+ printf >&2 "# proc-receive hook\n"
+
+ test-tool proc-receive -v \
+ -r "$ZERO_OID $A refs/heads/master ok"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : (B) refs/for/master/topic
+test_expect_success "proc-receive: warning on report for builtin command" '
+ test_must_fail git -C workbench push origin \
+ $B:refs/heads/master \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> <ZERO-OID> <COMMIT-A> refs/heads/master ok
+ warning: proc-receive reported status on ref of builtin command: refs/heads/master
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ To ../upstream
+ <OID>..<OID> <COMMIT-B> -> master
+ ! [remote rejected] HEAD -> refs/for/master/topic (no report from proc-receive)
+ error: failed to push some refs to "../upstream"
+ EOF
+ test_sorted_cmp expect actual &&
+ git -C upstream show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-B> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "cleanup" '
+ git -C upstream update-ref refs/heads/master $A
+'
+
+test_expect_success "setup proc-receive hook (ok)" '
+ cat >upstream/hooks/proc-receive <<-EOF
+ #!/bin/sh
+
+ printf >&2 "# proc-receive hook\n"
+
+ test-tool proc-receive -v \
+ -r "$ZERO_OID $A refs/for/master/topic ok"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic
+test_expect_success "proc-receive: ok" '
+ git -C workbench push origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ To ../upstream
+ * [new reference] HEAD -> refs/for/master/topic
+ EOF
+ test_cmp expect actual &&
+ git -C upstream show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/a/b/c/my/topic
+test_expect_success "proc-receive: no report from proc-receive" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/a/b/c/my/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic
+ remote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok
+ warning: proc-receive reported status on unknown ref: refs/for/master/topic
+ To ../upstream
+ ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (no report from proc-receive)
+ error: failed to push some refs to "../upstream"
+ EOF
+ test_sorted_cmp expect actual &&
+ git -C upstream show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push -o ... : refs/for/master/topic
+test_expect_success "not support push options" '
+ test_must_fail git -C workbench push \
+ -o issue=123 \
+ -o reviewer=user1 \
+ origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ fatal: the receiving end does not support push options
+ fatal: the remote end hung up unexpectedly
+ EOF
+ test_cmp expect actual &&
+ git -C upstream show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "enable push options" '
+ git -C upstream config receive.advertisePushOptions true
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push -o ... : next(A) refs/for/master/topic
+test_expect_success "push with options" '
+ git -C workbench push \
+ --atomic \
+ -o issue=123 \
+ -o reviewer=user1 \
+ origin \
+ HEAD:refs/heads/next \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive: atomic push_options
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive< issue=123
+ remote: proc-receive< reviewer=user1
+ remote: proc-receive> <ZERO-OID> <COMMIT-A> refs/for/master/topic ok
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ To ../upstream
+ * [new branch] HEAD -> next
+ * [new reference] HEAD -> refs/for/master/topic
+ EOF
+ test_cmp expect actual &&
+ git -C upstream show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ <COMMIT-A> refs/heads/next
+ EOF
+ test_cmp expect actual
+'
+
test_done