@@ -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
@@ -312,7 +312,8 @@ 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:1;
int index;
struct object_id old_oid;
struct object_id new_oid;
@@ -817,6 +818,241 @@ static int run_update_hook(struct command *cmd)
return finish_command(&proc);
}
+static int read_proc_receive_result(struct packet_reader *reader,
+ struct command **commands)
+{
+ struct command **tail = commands;
+ int code = 0;
+
+ for (;;) {
+ struct object_id old_oid, new_oid;
+ struct command *cmd;
+ 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);
+
+ FLEX_ALLOC_MEM(cmd, ref_name, refname, strlen(refname));
+ oidcpy(&cmd->old_oid, &old_oid);
+ oidcpy(&cmd->new_oid, &new_oid);
+ cmd->run_proc_receive = 1;
+
+ if (!strcmp(status, "ng")) {
+ if (msg)
+ cmd->error_string = xstrdup(msg);
+ else
+ cmd->error_string = "failed";
+ code = 1;
+ } else if (strcmp("ok", status)) {
+ die("protocol error: proc-receive has bad status '%s' for '%s'",
+ status, reader->line);
+ }
+
+ *tail = cmd;
+ tail = &cmd->next;
+ }
+ 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 *result_commands = NULL;
+ 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_push_options)
+ strbuf_addstr(&cap, " push-options");
+ if (use_atomic)
+ strbuf_addstr(&cap, " atomic");
+ 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, &result_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);
+
+ /* After receiving the result from the "proc-receive" hook,
+ * "receive-pack" will use the result to replace commands that
+ * have specific `run_proc_receive` field.
+ */
+ for (cmd = *commands; cmd; cmd = cmd->next)
+ if (!cmd->run_proc_receive)
+ break;
+
+ /* Merge commands with result_commands and sort */
+ if (!cmd) {
+ *commands = result_commands;
+ } else {
+ struct command *next_cmd = cmd;
+ struct command *next_result = result_commands;
+ struct command *head = NULL;
+ struct command *tail = NULL;
+
+ if (!next_result ||
+ strcmp(next_cmd->ref_name, next_result->ref_name) < 0) {
+ head = next_cmd;
+ next_cmd = next_cmd->next;
+ } else {
+ head = next_result;
+ next_result = next_result->next;
+ }
+ tail = head;
+
+ for (;;) {
+ if (!next_cmd) {
+ tail->next = next_result;
+ break;
+ } else if (next_cmd->run_proc_receive) {
+ next_cmd = next_cmd->next;
+ } else if (!next_result) {
+ tail->next = next_cmd;
+ next_cmd = next_cmd->next;
+ tail = tail->next;
+ } else {
+ if (strcmp(next_cmd->ref_name, next_result->ref_name) < 0) {
+ tail->next = next_cmd;
+ next_cmd = next_cmd->next;
+ tail = tail->next;
+ } else {
+ tail->next = next_result;
+ next_result = next_result->next;
+ tail = tail->next;
+ }
+ }
+ }
+ *commands = head;
+ }
+
+ 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 +1628,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 +1668,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);
@@ -1458,16 +1694,18 @@ static void execute_commands_atomic(struct command *commands,
strbuf_release(&err);
}
-static void execute_commands(struct command *commands,
+static void execute_commands(struct command **orig_commands,
const char *unpacker_error,
struct shallow_info *si,
const struct string_list *push_options)
{
+ struct command *commands = *orig_commands;
struct check_connected_options opt = CHECK_CONNECTED_INIT;
struct command *cmd;
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 +1735,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 +1775,19 @@ 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(orig_commands, push_options);
+ commands = *orig_commands;
+ 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
@@ -2019,7 +2284,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
update_shallow_info(commands, &si, &ref);
}
use_keepalive = KEEPALIVE_ALWAYS;
- execute_commands(commands, unpack_status, &si,
+ execute_commands(&commands, unpack_status, &si,
&push_options);
if (pack_lockfile)
unlink_or_warn(pack_lockfile);
new file mode 100644
@@ -0,0 +1,164 @@
+#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_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, "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;
+
+ 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);
@@ -50,6 +50,24 @@ format_git_output () {
-e "s/'/\"/g"
}
+# Asynchronous sideband may generate inconsistent output messages,
+# sort before comparison.
+test_sorted_cmp () {
+ if ! $GIT_TEST_CMP "$@"
+ then
+ cmd=$GIT_TEST_CMP
+ for f in "$@"
+ do
+ sort "$f" >"$f.sorted"
+ cmd="$cmd \"$f.sorted\""
+ done
+ if ! eval $cmd
+ then
+ $GIT_TEST_CMP "$@"
+ fi
+ fi
+}
+
test_expect_success "setup" '
git init --bare upstream &&
git init workbench &&
@@ -141,4 +159,514 @@ test_expect_success "normal git-push command" '
test_cmp expect actual
'
+test_expect_success "cleanup" '
+ (
+ cd upstream &&
+ git update-ref -d refs/review/master/topic &&
+ git update-ref -d refs/tags/v1.0.0 &&
+ git update-ref -d refs/heads/a/b/c
+ )
+'
+
+test_expect_success "no proc-receive hook, fail to push special ref" '
+ (
+ cd workbench &&
+ test_must_fail git push origin \
+ HEAD:next \
+ HEAD:refs/for/master/topic
+ ) >out 2>&1 &&
+ format_git_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 &&
+ (
+ cd upstream &&
+ git show-ref
+ ) >out &&
+ format_git_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ <COMMIT-A> refs/heads/next
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "cleanup" '
+ (
+ cd upstream &&
+ git update-ref -d refs/heads/next
+ )
+'
+
+# TODO: report for the failure of master branch is unnecessary.
+test_expect_success "no proc-receive hook, fail all for atomic push" '
+ (
+ cd workbench &&
+ test_must_fail git push --atomic origin \
+ HEAD:next \
+ HEAD:refs/for/master/topic
+ ) >out 2>&1 &&
+ format_git_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
+ ! [rejected] master (atomic push failed)
+ ! [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 &&
+ (
+ cd upstream &&
+ git show-ref
+ ) >out &&
+ format_git_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
+'
+
+test_expect_success "proc-receive bad protocol: unknown version" '
+ (
+ cd workbench &&
+ test_must_fail git push origin \
+ HEAD:refs/for/master/topic
+ ) >out 2>&1 &&
+ format_git_output <out | grep "protocol error" >actual &&
+ cat >expect <<-EOF &&
+ fatal: protocol error: unknown proc-receive version "2"
+ EOF
+ test_cmp expect actual &&
+ (
+ cd upstream &&
+ git show-ref
+ ) >out &&
+ format_git_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
+'
+
+test_expect_success "proc-receive bad protocol: no report" '
+ (
+ cd workbench &&
+ test_must_fail git push origin \
+ HEAD:refs/for/master/topic
+ ) >out 2>&1 &&
+ format_git_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
+ To ../upstream
+ ! [remote failure] HEAD -> refs/for/master/topic (remote failed to report status)
+ error: failed to push some refs to "../upstream"
+ EOF
+ test_cmp expect actual &&
+ (
+ cd upstream &&
+ git show-ref
+ ) >out &&
+ format_git_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+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
+'
+
+test_expect_success "proc-receive bad protocol: bad oid" '
+ (
+ cd workbench &&
+ test_must_fail git push origin \
+ HEAD:refs/for/master/topic
+ ) >out 2>&1 &&
+ format_git_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 &&
+ (
+ cd upstream &&
+ git show-ref
+ ) >out &&
+ format_git_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
+'
+
+test_expect_success "proc-receive bad protocol: no status" '
+ (
+ cd workbench &&
+ test_must_fail git push origin \
+ HEAD:refs/for/master/topic
+ ) >out 2>&1 &&
+ format_git_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 &&
+ (
+ cd upstream &&
+ git show-ref
+ ) >out &&
+ format_git_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
+'
+
+test_expect_success "proc-receive bad protocol: unknown status" '
+ (
+ cd workbench &&
+ test_must_fail git push origin \
+ HEAD:refs/for/master/topic
+ ) >out 2>&1 &&
+ format_git_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 &&
+ (
+ cd upstream &&
+ git show-ref
+ ) >out &&
+ format_git_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
+'
+
+test_expect_success "proc-receive bad protocol: bad status" '
+ (
+ cd workbench &&
+ test_must_fail git push origin \
+ HEAD:refs/for/master/topic
+ ) >out 2>&1 &&
+ format_git_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 &&
+ (
+ cd upstream &&
+ git show-ref
+ ) >out &&
+ format_git_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
+'
+
+test_expect_success "proc-receive: fail to update (no message)" '
+ (
+ cd workbench &&
+ test_must_fail git push origin \
+ HEAD:refs/for/master/topic
+ ) >out 2>&1 &&
+ format_git_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 &&
+ (
+ cd upstream &&
+ git show-ref
+ ) >out &&
+ format_git_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
+'
+
+test_expect_success "proc-receive: fail to update (has message)" '
+ (
+ cd workbench &&
+ test_must_fail git push origin \
+ HEAD:refs/for/master/topic
+ ) >out 2>&1 &&
+ format_git_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 &&
+ (
+ cd upstream &&
+ git show-ref
+ ) >out &&
+ format_git_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+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
+'
+
+test_expect_success "proc-receive: ok" '
+ (
+ cd workbench &&
+ git push origin \
+ HEAD:refs/for/master/topic
+ ) >out 2>&1 &&
+ format_git_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 &&
+ (
+ cd upstream &&
+ git show-ref
+ ) >out &&
+ format_git_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "proc-receive: report unknown ref" '
+ (
+ cd workbench &&
+ test_must_fail git push origin \
+ HEAD:refs/for/a/b/c/my/topic
+ ) >out 2>&1 &&
+ format_git_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: remote reported status on unknown ref: refs/for/master/topic
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ To ../upstream
+ ! [remote failure] HEAD -> refs/for/a/b/c/my/topic (remote failed to report status)
+ error: failed to push some refs to "../upstream"
+ EOF
+ test_cmp expect actual &&
+ (
+ cd upstream &&
+ git show-ref
+ ) >out &&
+ format_git_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "not support push options" '
+ (
+ cd workbench &&
+ test_must_fail git push \
+ -o issue=123 \
+ -o reviewer=user1 \
+ origin \
+ HEAD:refs/for/master/topic
+ ) >out 2>&1 &&
+ format_git_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 &&
+ (
+ cd upstream &&
+ git show-ref
+ ) >out &&
+ format_git_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "enable push options" '
+ (
+ cd upstream &&
+ git config receive.advertisePushOptions true
+ )
+'
+
+test_expect_success "push with options" '
+ (
+ cd workbench &&
+ git push \
+ -o issue=123 \
+ -o reviewer=user1 \
+ origin \
+ HEAD:refs/heads/next \
+ HEAD:refs/for/master/topic
+ ) >out 2>&1 &&
+ format_git_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: 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/for/master/topic
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ To ../upstream
+ * [new branch] HEAD -> next
+ * [new reference] HEAD -> refs/for/master/topic
+ EOF
+ test_cmp expect actual &&
+ (
+ cd upstream &&
+ git show-ref
+ ) >out &&
+ format_git_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/heads/master
+ <COMMIT-A> refs/heads/next
+ EOF
+ test_cmp expect actual
+'
+
test_done