summaryrefslogtreecommitdiff
path: root/t
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2020-09-25 22:25:39 (GMT)
committerJunio C Hamano <gitster@pobox.com>2020-09-25 22:25:39 (GMT)
commit6c430a647cb990fc856d328733fa59e1fafadb97 (patch)
tree09bf6873a1b13f2f0178d864a09a30f9fe3cf09a /t
parent48794acc50f14394ca6c4f5092a4a498f409f350 (diff)
parentd6edc189f6b3d8606150a52fd2c2fcd781b8550f (diff)
downloadgit-6c430a647cb990fc856d328733fa59e1fafadb97.zip
git-6c430a647cb990fc856d328733fa59e1fafadb97.tar.gz
git-6c430a647cb990fc856d328733fa59e1fafadb97.tar.bz2
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
Diffstat (limited to 't')
-rw-r--r--t/helper/test-proc-receive.c176
-rw-r--r--t/helper/test-tool.c1
-rw-r--r--t/helper/test-tool.h1
-rwxr-xr-xt/t5411-proc-receive-hook.sh117
-rw-r--r--t/t5411/common-functions.sh56
-rw-r--r--t/t5411/once-0010-report-status-v1.sh94
-rw-r--r--t/t5411/test-0000-standard-git-push.sh143
-rw-r--r--t/t5411/test-0001-standard-git-push--porcelain.sh147
-rw-r--r--t/t5411/test-0002-pre-receive-declined.sh33
-rw-r--r--t/t5411/test-0003-pre-receive-declined--porcelain.sh34
-rw-r--r--t/t5411/test-0010-proc-receive-settings.sh7
-rw-r--r--t/t5411/test-0011-no-hook-error.sh64
-rw-r--r--t/t5411/test-0012-no-hook-error--porcelain.sh66
-rw-r--r--t/t5411/test-0013-bad-protocol.sh217
-rw-r--r--t/t5411/test-0014-bad-protocol--porcelain.sh160
-rw-r--r--t/t5411/test-0020-report-ng.sh67
-rw-r--r--t/t5411/test-0021-report-ng--porcelain.sh69
-rw-r--r--t/t5411/test-0022-report-unexpect-ref.sh45
-rw-r--r--t/t5411/test-0023-report-unexpect-ref--porcelain.sh46
-rw-r--r--t/t5411/test-0024-report-unknown-ref.sh34
-rw-r--r--t/t5411/test-0025-report-unknown-ref--porcelain.sh35
-rw-r--r--t/t5411/test-0026-push-options.sh79
-rw-r--r--t/t5411/test-0027-push-options--porcelain.sh82
-rw-r--r--t/t5411/test-0030-report-ok.sh35
-rw-r--r--t/t5411/test-0031-report-ok--porcelain.sh36
-rw-r--r--t/t5411/test-0032-report-with-options.sh256
-rw-r--r--t/t5411/test-0033-report-with-options--porcelain.sh265
-rw-r--r--t/t5411/test-0034-report-ft.sh44
-rw-r--r--t/t5411/test-0035-report-ft--porcelain.sh45
-rw-r--r--t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh227
-rw-r--r--t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh172
-rw-r--r--t/t5411/test-0038-report-mixed-refs.sh89
-rw-r--r--t/t5411/test-0039-report-mixed-refs--porcelain.sh91
-rw-r--r--t/t5411/test-0040-process-all-refs.sh113
-rw-r--r--t/t5411/test-0041-process-all-refs--porcelain.sh114
-rw-r--r--t/t5411/test-0050-proc-receive-refs-with-modifiers.sh135
-rwxr-xr-xt/t5516-fetch-push.sh2
37 files changed, 3396 insertions, 1 deletions
diff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c
new file mode 100644
index 0000000..42164d9
--- /dev/null
+++ b/t/helper/test-proc-receive.c
@@ -0,0 +1,176 @@
+#include "cache.h"
+#include "connect.h"
+#include "parse-options.h"
+#include "pkt-line.h"
+#include "sigchain.h"
+#include "test-tool.h"
+
+static const char *proc_receive_usage[] = {
+ "test-tool proc-receive [<options>...]",
+ NULL
+};
+
+static int die_version;
+static int die_readline;
+static int no_push_options;
+static int use_atomic;
+static int use_push_options;
+static int verbose;
+static int version = 1;
+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_version)
+ 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_readline)
+ die("protocol error: expected 'old new ref', got '%s'",
+ reader->line);
+ refname = p;
+ FLEX_ALLOC_STR(cmd, ref_name, 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)
+{
+ int nongit_ok = 0;
+ struct packet_reader reader;
+ struct command *commands = NULL;
+ 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_BOOL(0, "die-version", &die_version,
+ "die during version negotiation"),
+ OPT_BOOL(0, "die-readline", &die_readline,
+ "die when readline"),
+ 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()
+ };
+
+ setup_git_directory_gently(&nongit_ok);
+
+ 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);
+
+ sigchain_push(SIGPIPE, SIG_IGN);
+ 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)
+ fprintf(stderr, "proc-receive< %s %s %s\n",
+ oid_to_hex(&cmd->old_oid),
+ oid_to_hex(&cmd->new_oid),
+ 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);
+ sigchain_pop(SIGPIPE);
+
+ return 0;
+}
diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c
index 590b2ef..a0d3966 100644
--- a/t/helper/test-tool.c
+++ b/t/helper/test-tool.c
@@ -46,6 +46,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 },
diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h
index ddc8e99..07034d3 100644
--- a/t/helper/test-tool.h
+++ b/t/helper/test-tool.h
@@ -35,6 +35,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);
diff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh
new file mode 100755
index 0000000..7464872
--- /dev/null
+++ b/t/t5411-proc-receive-hook.sh
@@ -0,0 +1,117 @@
+#!/bin/sh
+#
+# Copyright (c) 2020 Jiang Xin
+#
+
+test_description='Test proc-receive hook'
+
+. ./test-lib.sh
+
+. "$TEST_DIRECTORY"/t5411/common-functions.sh
+
+setup_upstream_and_workbench () {
+ # Refs of upstream : master(A)
+ # Refs of workbench: master(A) tags/v123
+ test_expect_success "setup upstream and workbench" '
+ rm -rf upstream.git &&
+ rm -rf workbench &&
+ git init --bare upstream.git &&
+ git init workbench &&
+ create_commits_in workbench A B &&
+ (
+ cd workbench &&
+ # Try to make a stable fixed width for abbreviated commit ID,
+ # this fixed-width oid will be replaced with "<OID>".
+ git config core.abbrev 7 &&
+ git tag -m "v123" v123 $A &&
+ git remote add origin ../upstream.git &&
+ git push origin master &&
+ git update-ref refs/heads/master $A $B &&
+ git -C ../upstream.git update-ref \
+ refs/heads/master $A $B
+ ) &&
+ TAG=$(git -C workbench rev-parse v123) &&
+
+ # setup pre-receive hook
+ write_script upstream.git/hooks/pre-receive <<-\EOF &&
+ exec >&2
+ echo "# pre-receive hook"
+ while read old new ref
+ do
+ echo "pre-receive< $old $new $ref"
+ done
+ EOF
+
+ # setup post-receive hook
+ write_script upstream.git/hooks/post-receive <<-\EOF &&
+ exec >&2
+ echo "# post-receive hook"
+ while read old new ref
+ do
+ echo "post-receive< $old $new $ref"
+ done
+ EOF
+
+ upstream=upstream.git
+ '
+}
+
+run_proc_receive_hook_test() {
+ case $1 in
+ http)
+ PROTOCOL="HTTP protocol"
+ URL_PREFIX="http://.*"
+ ;;
+ local)
+ PROTOCOL="builtin protocol"
+ URL_PREFIX="\.\."
+ ;;
+ esac
+
+ # Include test cases for both file and HTTP protocol
+ for t in "$TEST_DIRECTORY"/t5411/test-*.sh
+ do
+ . "$t"
+ done
+}
+
+# Initialize the upstream repository and local workbench.
+setup_upstream_and_workbench
+
+# Load test cases that only need to be executed once.
+for t in "$TEST_DIRECTORY"/t5411/once-*.sh
+do
+ . "$t"
+done
+
+# Initialize the upstream repository and local workbench.
+setup_upstream_and_workbench
+
+# Run test cases for 'proc-receive' hook on local file protocol.
+run_proc_receive_hook_test local
+
+ROOT_PATH="$PWD"
+. "$TEST_DIRECTORY"/lib-gpg.sh
+. "$TEST_DIRECTORY"/lib-httpd.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
+start_httpd
+
+# Re-initialize the upstream repository and local workbench.
+setup_upstream_and_workbench
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "setup for HTTP protocol" '
+ git -C upstream.git config http.receivepack true &&
+ upstream="$HTTPD_DOCUMENT_ROOT_PATH/upstream.git" &&
+ mv upstream.git "$upstream" &&
+ git -C workbench remote set-url origin "$HTTPD_URL/auth-push/smart/upstream.git" &&
+ set_askpass user@host pass@host
+'
+
+setup_askpass_helper
+
+# Run test cases for 'proc-receive' hook on HTTP protocol.
+run_proc_receive_hook_test http
+
+test_done
diff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh
new file mode 100644
index 0000000..6580beb
--- /dev/null
+++ b/t/t5411/common-functions.sh
@@ -0,0 +1,56 @@
+# Create commits in <repo> and assign each commit's oid to shell variables
+# given in the arguments (A, B, and C). E.g.:
+#
+# create_commits_in <repo> A B C
+#
+# NOTE: Never calling this function from a subshell since variable
+# assignments will disappear when subshell exits.
+create_commits_in () {
+ repo="$1" &&
+ if ! parent=$(git -C "$repo" rev-parse HEAD^{} --)
+ then
+ parent=
+ fi &&
+ T=$(git -C "$repo" write-tree) &&
+ shift &&
+ while test $# -gt 0
+ do
+ name=$1 &&
+ test_tick &&
+ if test -z "$parent"
+ then
+ oid=$(echo $name | git -C "$repo" commit-tree $T)
+ else
+ oid=$(echo $name | git -C "$repo" commit-tree -p $parent $T)
+ fi &&
+ eval $name=$oid &&
+ parent=$oid &&
+ shift ||
+ return 1
+ done &&
+ git -C "$repo" update-ref refs/heads/master $oid
+}
+
+# Format the output of git-push, git-show-ref and other commands to make a
+# user-friendly and stable text. We can easily prepare the expect text
+# without having to worry about future changes of the commit ID and spaces
+# of the output. Single quotes are replaced with double quotes, because
+# it is boring to prepare unquoted single quotes in expect text. We also
+# remove some locale error messages, which break test if we turn on
+# `GIT_TEST_GETTEXT_POISON=true` in order to test unintentional translations
+# on plumbing commands.
+make_user_friendly_and_stable_output () {
+ sed \
+ -e "s/ *\$//" \
+ -e "s/ */ /g" \
+ -e "s/'/\"/g" \
+ -e "s/ / /g" \
+ -e "s/$A/<COMMIT-A>/g" \
+ -e "s/$B/<COMMIT-B>/g" \
+ -e "s/$TAG/<TAG-v123>/g" \
+ -e "s/$ZERO_OID/<ZERO-OID>/g" \
+ -e "s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g" \
+ -e "s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g" \
+ -e "s#To $URL_PREFIX/upstream.git#To <URL/of/upstream.git>#" \
+ -e "/^error: / d"
+}
diff --git a/t/t5411/once-0010-report-status-v1.sh b/t/t5411/once-0010-report-status-v1.sh
new file mode 100644
index 0000000..dc2cf4a
--- /dev/null
+++ b/t/t5411/once-0010-report-status-v1.sh
@@ -0,0 +1,94 @@
+test_expect_success "setup receive.procReceiveRefs" '
+ git -C "$upstream" config --add receive.procReceiveRefs refs/for
+'
+
+test_expect_success "setup proc-receive hook" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic1" \
+ -r "option fall-through" \
+ -r "ok refs/for/master/topic2" \
+ -r "option refname refs/for/changes/23/123/1" \
+ -r "option new-oid $A" \
+ -r "ok refs/for/master/topic2" \
+ -r "option refname refs/for/changes/24/124/2" \
+ -r "option old-oid $B" \
+ -r "option new-oid $A" \
+ -r "option forced-update" \
+ -r "ng refs/for/next/topic target branch not exist"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : (B) refs/for/master/topic1(A) foo(A) refs/for/next/topic(A) refs/for/master/topic2(A)
+test_expect_success "proc-receive: report status v1" '
+ {
+ if test -z "$GIT_DEFAULT_HASH" || test "$GIT_DEFAULT_HASH" = "sha1"
+ then
+ printf "%s %s refs/heads/master\0report-status\n" \
+ $A $B | packetize
+ else
+ printf "%s %s refs/heads/master\0report-status object-format=$GIT_DEFAULT_HASH\n" \
+ $A $B | packetize
+ fi &&
+ printf "%s %s refs/for/master/topic1\n" \
+ $ZERO_OID $A | packetize &&
+ printf "%s %s refs/heads/foo\n" \
+ $ZERO_OID $A | packetize &&
+ printf "%s %s refs/for/next/topic\n" \
+ $ZERO_OID $A | packetize &&
+ printf "%s %s refs/for/master/topic2\n" \
+ $ZERO_OID $A | packetize &&
+ printf 0000 &&
+ printf "" | git -C "$upstream" pack-objects --stdout
+ } | git receive-pack "$upstream" --stateless-rpc \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ # pre-receive hook
+ pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1
+ pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo
+ pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+ pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2
+ # proc-receive hook
+ proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1
+ proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+ proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2
+ proc-receive> ok refs/for/master/topic1
+ proc-receive> option fall-through
+ proc-receive> ok refs/for/master/topic2
+ proc-receive> option refname refs/for/changes/23/123/1
+ proc-receive> option new-oid <COMMIT-A>
+ proc-receive> ok refs/for/master/topic2
+ proc-receive> option refname refs/for/changes/24/124/2
+ proc-receive> option old-oid <COMMIT-B>
+ proc-receive> option new-oid <COMMIT-A>
+ proc-receive> option forced-update
+ proc-receive> ng refs/for/next/topic target branch not exist
+ 000eunpack ok
+ 0019ok refs/heads/master
+ 001eok refs/for/master/topic1
+ 0016ok refs/heads/foo
+ 0033ng refs/for/next/topic target branch not exist
+ 001eok refs/for/master/topic2
+ 0000# post-receive hook
+ post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1
+ post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo
+ post-receive< <ZERO-OID> <COMMIT-A> refs/for/changes/23/123/1
+ post-receive< <COMMIT-B> <COMMIT-A> refs/for/changes/24/124/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/for/master/topic1
+ <COMMIT-A> refs/heads/foo
+ <COMMIT-B> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
diff --git a/t/t5411/test-0000-standard-git-push.sh b/t/t5411/test-0000-standard-git-push.sh
new file mode 100644
index 0000000..e206587
--- /dev/null
+++ b/t/t5411/test-0000-standard-git-push.sh
@@ -0,0 +1,143 @@
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git-push : master(B) next(A)
+test_expect_success "git-push ($PROTOCOL)" '
+ git -C workbench push origin \
+ $B:refs/heads/master \
+ HEAD:refs/heads/next \
+ >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/heads/next
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ To <URL/of/upstream.git>
+ <OID-A>..<OID-B> <COMMIT-B> -> master
+ * [new branch] HEAD -> next
+ EOF
+ test_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
+ <COMMIT-A> refs/heads/next
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(B) next(A)
+# Refs of workbench: master(A) tags/v123
+# git-push --atomic: master(A) next(B)
+test_expect_success "git-push --atomic ($PROTOCOL)" '
+ test_must_fail git -C workbench push --atomic origin \
+ master \
+ $B:refs/heads/next \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out |
+ sed -n \
+ -e "/^To / { s/ */ /g; p; }" \
+ -e "/^ ! / { s/ */ /g; p; }" \
+ >actual &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ ! [rejected] master -> master (non-fast-forward)
+ ! [rejected] <COMMIT-B> -> next (atomic push failed)
+ EOF
+ test_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
+ <COMMIT-A> refs/heads/next
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(B) next(A)
+# Refs of workbench: master(A) tags/v123
+# git-push : master(A) next(B)
+test_expect_success "non-fast-forward git-push ($PROTOCOL)" '
+ test_must_fail git \
+ -C workbench \
+ -c advice.pushUpdateRejected=false \
+ push origin \
+ master \
+ $B:refs/heads/next \
+ >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/next
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/next
+ To <URL/of/upstream.git>
+ <OID-A>..<OID-B> <COMMIT-B> -> next
+ ! [rejected] master -> master (non-fast-forward)
+ EOF
+ test_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
+ <COMMIT-B> refs/heads/next
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(B) next(B)
+# Refs of workbench: master(A) tags/v123
+# git-push -f : master(A) NULL tags/v123 refs/review/master/topic(A) a/b/c(A)
+test_expect_success "git-push -f ($PROTOCOL)" '
+ git -C workbench push -f origin \
+ refs/tags/v123 \
+ :refs/heads/next \
+ master \
+ master:refs/review/master/topic \
+ HEAD:refs/heads/a/b/c \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+ remote: pre-receive< <COMMIT-B> <ZERO-OID> refs/heads/next
+ remote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+ remote: post-receive< <COMMIT-B> <ZERO-OID> refs/heads/next
+ remote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c
+ To <URL/of/upstream.git>
+ + <OID-B>...<OID-A> master -> master (forced update)
+ - [deleted] next
+ * [new tag] v123 -> v123
+ * [new reference] master -> refs/review/master/topic
+ * [new branch] HEAD -> a/b/c
+ 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/a/b/c
+ <COMMIT-A> refs/heads/master
+ <COMMIT-A> refs/review/master/topic
+ <TAG-v123> refs/tags/v123
+ EOF
+ 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 ($PROTOCOL)" '
+ (
+ 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
+ )
+'
diff --git a/t/t5411/test-0001-standard-git-push--porcelain.sh b/t/t5411/test-0001-standard-git-push--porcelain.sh
new file mode 100644
index 0000000..48f6fcc
--- /dev/null
+++ b/t/t5411/test-0001-standard-git-push--porcelain.sh
@@ -0,0 +1,147 @@
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git-push : master(B) next(A)
+test_expect_success "git-push ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain origin \
+ $B:refs/heads/master \
+ HEAD:refs/heads/next \
+ >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/heads/next
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ To <URL/of/upstream.git>
+ <COMMIT-B>:refs/heads/master <OID-A>..<OID-B>
+ * HEAD:refs/heads/next [new branch]
+ Done
+ EOF
+ test_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
+ <COMMIT-A> refs/heads/next
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(B) next(A)
+# Refs of workbench: master(A) tags/v123
+# git-push --atomic: master(A) next(B)
+test_expect_success "git-push --atomic ($PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --atomic --porcelain origin \
+ master \
+ $B:refs/heads/next \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out |
+ sed -n \
+ -e "s/^# GETTEXT POISON #//" \
+ -e "/^To / { s/ */ /g; p; }" \
+ -e "/^! / { s/ */ /g; p; }" \
+ >actual &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ ! refs/heads/master:refs/heads/master [rejected] (non-fast-forward)
+ ! <COMMIT-B>:refs/heads/next [rejected] (atomic push failed)
+ EOF
+ test_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
+ <COMMIT-A> refs/heads/next
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(B) next(A)
+# Refs of workbench: master(A) tags/v123
+# git-push : master(A) next(B)
+test_expect_success "non-fast-forward git-push ($PROTOCOL/porcelain)" '
+ test_must_fail git \
+ -C workbench \
+ -c advice.pushUpdateRejected=false \
+ push --porcelain origin \
+ master \
+ $B:refs/heads/next \
+ >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/next
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/next
+ To <URL/of/upstream.git>
+ <COMMIT-B>:refs/heads/next <OID-A>..<OID-B>
+ ! refs/heads/master:refs/heads/master [rejected] (non-fast-forward)
+ Done
+ EOF
+ test_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
+ <COMMIT-B> refs/heads/next
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(B) next(B)
+# Refs of workbench: master(A) tags/v123
+# git-push -f : master(A) NULL tags/v123 refs/review/master/topic(A) a/b/c(A)
+test_expect_success "git-push -f ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain -f origin \
+ refs/tags/v123 \
+ :refs/heads/next \
+ master \
+ master:refs/review/master/topic \
+ HEAD:refs/heads/a/b/c \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+ remote: pre-receive< <COMMIT-B> <ZERO-OID> refs/heads/next
+ remote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+ remote: post-receive< <COMMIT-B> <ZERO-OID> refs/heads/next
+ remote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c
+ To <URL/of/upstream.git>
+ + refs/heads/master:refs/heads/master <OID-B>...<OID-A> (forced update)
+ - :refs/heads/next [deleted]
+ * refs/tags/v123:refs/tags/v123 [new tag]
+ * refs/heads/master:refs/review/master/topic [new reference]
+ * HEAD:refs/heads/a/b/c [new branch]
+ Done
+ 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/a/b/c
+ <COMMIT-A> refs/heads/master
+ <COMMIT-A> refs/review/master/topic
+ <TAG-v123> refs/tags/v123
+ EOF
+ 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 ($PROTOCOL/porcelain)" '
+ (
+ 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
+ )
+'
diff --git a/t/t5411/test-0002-pre-receive-declined.sh b/t/t5411/test-0002-pre-receive-declined.sh
new file mode 100644
index 0000000..c246f7e
--- /dev/null
+++ b/t/t5411/test-0002-pre-receive-declined.sh
@@ -0,0 +1,33 @@
+test_expect_success "setup pre-receive hook ($PROTOCOL)" '
+ mv "$upstream/hooks/pre-receive" "$upstream/hooks/pre-receive.ok" &&
+ write_script "$upstream/hooks/pre-receive" <<-EOF
+ exit 1
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git-push : master(B) next(A)
+test_expect_success "git-push is declined ($PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ $B:refs/heads/master \
+ HEAD:refs/heads/next \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ ! [remote rejected] <COMMIT-B> -> master (pre-receive hook declined)
+ ! [remote rejected] HEAD -> next (pre-receive hook declined)
+ 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 "cleanup ($PROTOCOL)" '
+ mv "$upstream/hooks/pre-receive.ok" "$upstream/hooks/pre-receive"
+'
diff --git a/t/t5411/test-0003-pre-receive-declined--porcelain.sh b/t/t5411/test-0003-pre-receive-declined--porcelain.sh
new file mode 100644
index 0000000..b14894d
--- /dev/null
+++ b/t/t5411/test-0003-pre-receive-declined--porcelain.sh
@@ -0,0 +1,34 @@
+test_expect_success "setup pre-receive hook ($PROTOCOL/porcelain)" '
+ mv "$upstream/hooks/pre-receive" "$upstream/hooks/pre-receive.ok" &&
+ write_script "$upstream/hooks/pre-receive" <<-EOF
+ exit 1
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git-push : master(B) next(A)
+test_expect_success "git-push is declined ($PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain origin \
+ $B:refs/heads/master \
+ HEAD:refs/heads/next \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ ! <COMMIT-B>:refs/heads/master [remote rejected] (pre-receive hook declined)
+ ! HEAD:refs/heads/next [remote rejected] (pre-receive hook declined)
+ Done
+ 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 "cleanup ($PROTOCOL/porcelain)" '
+ mv "$upstream/hooks/pre-receive.ok" "$upstream/hooks/pre-receive"
+'
diff --git a/t/t5411/test-0010-proc-receive-settings.sh b/t/t5411/test-0010-proc-receive-settings.sh
new file mode 100644
index 0000000..a368099
--- /dev/null
+++ b/t/t5411/test-0010-proc-receive-settings.sh
@@ -0,0 +1,7 @@
+test_expect_success "add two receive.procReceiveRefs settings" '
+ (
+ cd "$upstream" &&
+ git config --add receive.procReceiveRefs refs/for &&
+ git config --add receive.procReceiveRefs refs/review/
+ )
+'
diff --git a/t/t5411/test-0011-no-hook-error.sh b/t/t5411/test-0011-no-hook-error.sh
new file mode 100644
index 0000000..bb6ec92
--- /dev/null
+++ b/t/t5411/test-0011-no-hook-error.sh
@@ -0,0 +1,64 @@
+# 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: no hook, fail to push special ref ($PROTOCOL)" '
+ 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 find hook "proc-receive"
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ To <URL/of/upstream.git>
+ * [new branch] HEAD -> next
+ ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)
+ 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 ($PROTOCOL)" '
+ git -C "$upstream" update-ref -d refs/heads/next
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push --atomic: (B) next(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: no hook, all failed for atomic push ($PROTOCOL)" '
+ test_must_fail git -C workbench push --atomic origin \
+ $B:master \
+ 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< <COMMIT-A> <COMMIT-B> refs/heads/master
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: error: cannot find hook "proc-receive"
+ To <URL/of/upstream.git>
+ ! [remote rejected] <COMMIT-B> -> master (fail to run proc-receive hook)
+ ! [remote rejected] HEAD -> next (fail to run proc-receive hook)
+ ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)
+ 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
+'
diff --git a/t/t5411/test-0012-no-hook-error--porcelain.sh b/t/t5411/test-0012-no-hook-error--porcelain.sh
new file mode 100644
index 0000000..4814f74
--- /dev/null
+++ b/t/t5411/test-0012-no-hook-error--porcelain.sh
@@ -0,0 +1,66 @@
+# 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: no hook, fail to push special ref ($PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain 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 find hook "proc-receive"
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ To <URL/of/upstream.git>
+ * HEAD:refs/heads/next [new branch]
+ ! HEAD:refs/for/master/topic [remote rejected] (fail to run proc-receive hook)
+ Done
+ 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 ($PROTOCOL/porcelain)" '
+ git -C "$upstream" update-ref -d refs/heads/next
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push --atomic: (B) next(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: no hook, all failed for atomic push ($PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain --atomic origin \
+ $B:master \
+ 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< <COMMIT-A> <COMMIT-B> refs/heads/master
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: error: cannot find hook "proc-receive"
+ To <URL/of/upstream.git>
+ ! <COMMIT-B>:refs/heads/master [remote rejected] (fail to run proc-receive hook)
+ ! HEAD:refs/heads/next [remote rejected] (fail to run proc-receive hook)
+ ! HEAD:refs/for/master/topic [remote rejected] (fail to run proc-receive hook)
+ Done
+ 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
+'
diff --git a/t/t5411/test-0013-bad-protocol.sh b/t/t5411/test-0013-bad-protocol.sh
new file mode 100644
index 0000000..c5fe4cb
--- /dev/null
+++ b/t/t5411/test-0013-bad-protocol.sh
@@ -0,0 +1,217 @@
+test_expect_success "setup proc-receive hook (unknown version, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v --version 2
+ EOF
+'
+
+# 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, $PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+
+ # Check status report for git-push
+ sed -n \
+ -e "/^To / { p; n; p; }" \
+ <actual >actual-report &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)
+ EOF
+ test_cmp expect actual-report &&
+
+ # Check error message from "receive-pack", but ignore unstable fatal error
+ # message ("remote: fatal: the remote end hung up unexpectedly") which
+ # is different from the remote HTTP server with different locale settings.
+ grep "^remote: error:" <actual >actual-error &&
+ cat >expect <<-EOF &&
+ remote: error: proc-receive version "2" is not supported
+ EOF
+ test_cmp expect actual-error &&
+
+ 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 (hook --die-version, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v --die-version
+ EOF
+'
+
+# 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 (hook --die-version, $PROTOCOL)" '
+ 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: fatal: bad protocol version: 1
+ remote: error: proc-receive version "0" is not supported
+ To <URL/of/upstream.git>
+ ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)
+ 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 (hook --die-readline, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v --die-readline
+ EOF
+'
+
+# 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 (hook --die-readline, $PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+
+ grep "remote: fatal: protocol error: expected \"old new ref\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"" 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, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ 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, $PROTOCOL)" '
+ 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 <URL/of/upstream.git>
+ * [new branch] HEAD -> next
+ ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)
+ 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 ($PROTOCOL)" '
+ git -C "$upstream" update-ref -d refs/heads/next
+
+'
+
+test_expect_success "setup proc-receive hook (no ref, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "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 (no ref, $PROTOCOL)" '
+ 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> ok
+ remote: error: proc-receive reported incomplete status line: "ok"
+ To <URL/of/upstream.git>
+ ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)
+ 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, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "xx 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 (unknown status, $PROTOCOL)" '
+ 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> xx refs/for/master/topic
+ remote: error: proc-receive reported bad status "xx" on ref "refs/for/master/topic"
+ To <URL/of/upstream.git>
+ ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)
+ 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
+'
diff --git a/t/t5411/test-0014-bad-protocol--porcelain.sh b/t/t5411/test-0014-bad-protocol--porcelain.sh
new file mode 100644
index 0000000..53b47b0
--- /dev/null
+++ b/t/t5411/test-0014-bad-protocol--porcelain.sh
@@ -0,0 +1,160 @@
+test_expect_success "setup proc-receive hook (unknown version, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v --version 2
+ EOF
+'
+
+# 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, $PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+
+ # Check status report for git-push
+ sed -n \
+ -e "/^To / { p; n; p; n; p; }" \
+ <actual >actual-report &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ ! HEAD:refs/for/master/topic [remote rejected] (fail to run proc-receive hook)
+ Done
+ EOF
+ test_cmp expect actual-report &&
+
+ # Check error message from "receive-pack", but ignore unstable fatal error
+ # message ("remote: fatal: the remote end hung up unexpectedly") which
+ # is different from the remote HTTP server with different locale settings.
+ grep "^remote: error:" <actual >actual-error &&
+ cat >expect <<-EOF &&
+ remote: error: proc-receive version "2" is not supported
+ EOF
+ test_cmp expect actual-error &&
+
+ 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, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ 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, $PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain 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 <URL/of/upstream.git>
+ * HEAD:refs/heads/next [new branch]
+ ! HEAD:refs/for/master/topic [remote rejected] (proc-receive failed to report status)
+ Done
+ 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 ($PROTOCOL/porcelain)" '
+ git -C "$upstream" update-ref -d refs/heads/next
+
+'
+
+test_expect_success "setup proc-receive hook (no ref, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "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 (no ref, $PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain 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> ok
+ remote: error: proc-receive reported incomplete status line: "ok"
+ To <URL/of/upstream.git>
+ ! HEAD:refs/for/master/topic [remote rejected] (proc-receive failed to report status)
+ Done
+ 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, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "xx 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 (unknown status, $PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain 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> xx refs/for/master/topic
+ remote: error: proc-receive reported bad status "xx" on ref "refs/for/master/topic"
+ To <URL/of/upstream.git>
+ ! HEAD:refs/for/master/topic [remote rejected] (proc-receive failed to report status)
+ Done
+ 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
+'
diff --git a/t/t5411/test-0020-report-ng.sh b/t/t5411/test-0020-report-ng.sh
new file mode 100644
index 0000000..f726b7c
--- /dev/null
+++ b/t/t5411/test-0020-report-ng.sh
@@ -0,0 +1,67 @@
+test_expect_success "setup proc-receive hook (ng, no message, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ng 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: fail to update (ng, no message, $PROTOCOL)" '
+ 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> ng refs/for/master/topic
+ To <URL/of/upstream.git>
+ ! [remote rejected] HEAD -> refs/for/master/topic (failed)
+ 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, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ng refs/for/master/topic 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 (ng, with message, $PROTOCOL)" '
+ 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> ng refs/for/master/topic error msg
+ To <URL/of/upstream.git>
+ ! [remote rejected] HEAD -> refs/for/master/topic (error msg)
+ 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
+'
diff --git a/t/t5411/test-0021-report-ng--porcelain.sh b/t/t5411/test-0021-report-ng--porcelain.sh
new file mode 100644
index 0000000..fbf5569
--- /dev/null
+++ b/t/t5411/test-0021-report-ng--porcelain.sh
@@ -0,0 +1,69 @@
+test_expect_success "setup proc-receive hook (ng, no message, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ng 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: fail to update (ng, no message, $PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain 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> ng refs/for/master/topic
+ To <URL/of/upstream.git>
+ ! HEAD:refs/for/master/topic [remote rejected] (failed)
+ Done
+ 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, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ng refs/for/master/topic 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 (ng, with message, $PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain 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> ng refs/for/master/topic error msg
+ To <URL/of/upstream.git>
+ ! HEAD:refs/for/master/topic [remote rejected] (error msg)
+ Done
+ 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
+'
diff --git a/t/t5411/test-0022-report-unexpect-ref.sh b/t/t5411/test-0022-report-unexpect-ref.sh
new file mode 100644
index 0000000..92a415b
--- /dev/null
+++ b/t/t5411/test-0022-report-unexpect-ref.sh
@@ -0,0 +1,45 @@
+test_expect_success "setup proc-receive hook (unexpected ref, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/heads/master"
+ 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: report unexpected ref ($PROTOCOL)" '
+ 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> ok refs/heads/master
+ remote: error: proc-receive reported status on unexpected ref: refs/heads/master
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ To <URL/of/upstream.git>
+ <OID-A>..<OID-B> <COMMIT-B> -> master
+ ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)
+ EOF
+ test_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
+'
+
+# Refs of upstream : master(B)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL)" '
+ git -C "$upstream" update-ref refs/heads/master $A
+'
diff --git a/t/t5411/test-0023-report-unexpect-ref--porcelain.sh b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh
new file mode 100644
index 0000000..acbf93e
--- /dev/null
+++ b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh
@@ -0,0 +1,46 @@
+test_expect_success "setup proc-receive hook (unexpected ref, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/heads/master"
+ 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: report unexpected ref ($PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain 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> ok refs/heads/master
+ remote: error: proc-receive reported status on unexpected ref: refs/heads/master
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ To <URL/of/upstream.git>
+ <COMMIT-B>:refs/heads/master <OID-A>..<OID-B>
+ ! HEAD:refs/for/master/topic [remote rejected] (proc-receive failed to report status)
+ Done
+ EOF
+ test_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
+'
+
+# Refs of upstream : master(B)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+ git -C "$upstream" update-ref refs/heads/master $A
+'
diff --git a/t/t5411/test-0024-report-unknown-ref.sh b/t/t5411/test-0024-report-unknown-ref.sh
new file mode 100644
index 0000000..c3946f3
--- /dev/null
+++ b/t/t5411/test-0024-report-unknown-ref.sh
@@ -0,0 +1,34 @@
+test_expect_success "setup proc-receive hook (unexpected ref, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic"
+ EOF
+'
+
+# 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: report unknown reference ($PROTOCOL)" '
+ 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> ok refs/for/master/topic
+ remote: error: proc-receive reported status on unknown ref: refs/for/master/topic
+ To <URL/of/upstream.git>
+ ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (proc-receive failed to report status)
+ 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
+'
diff --git a/t/t5411/test-0025-report-unknown-ref--porcelain.sh b/t/t5411/test-0025-report-unknown-ref--porcelain.sh
new file mode 100644
index 0000000..d093b1a
--- /dev/null
+++ b/t/t5411/test-0025-report-unknown-ref--porcelain.sh
@@ -0,0 +1,35 @@
+test_expect_success "setup proc-receive hook (unexpected ref, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic"
+ EOF
+'
+
+# 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: report unknown reference ($PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain 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> ok refs/for/master/topic
+ remote: error: proc-receive reported status on unknown ref: refs/for/master/topic
+ To <URL/of/upstream.git>
+ ! HEAD:refs/for/a/b/c/my/topic [remote rejected] (proc-receive failed to report status)
+ Done
+ 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
+'
diff --git a/t/t5411/test-0026-push-options.sh b/t/t5411/test-0026-push-options.sh
new file mode 100644
index 0000000..d0c4da8
--- /dev/null
+++ b/t/t5411/test-0026-push-options.sh
@@ -0,0 +1,79 @@
+test_expect_success "setup proc-receive hook and disable push-options ($PROTOCOL)" '
+ git -C "$upstream" config receive.advertisePushOptions false &&
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push -o ... : refs/for/master/topic
+test_expect_success "proc-receive: not support push options ($PROTOCOL)" '
+ 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 &&
+ test_i18ngrep "fatal: the receiving end does not support push options" \
+ 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 ($PROTOCOL)" '
+ 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 "proc-receive: push with options ($PROTOCOL)" '
+ 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> ok refs/for/master/topic
+ 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 <URL/of/upstream.git>
+ * [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
+'
+
+# Refs of upstream : master(A) next(A)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL)" '
+ git -C "$upstream" update-ref -d refs/heads/next
+'
diff --git a/t/t5411/test-0027-push-options--porcelain.sh b/t/t5411/test-0027-push-options--porcelain.sh
new file mode 100644
index 0000000..c89a1e7
--- /dev/null
+++ b/t/t5411/test-0027-push-options--porcelain.sh
@@ -0,0 +1,82 @@
+test_expect_success "setup proc-receive hook and disable push-options ($PROTOCOL/porcelain)" '
+ git -C "$upstream" config receive.advertisePushOptions false &&
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push -o ... : refs/for/master/topic
+test_expect_success "proc-receive: not support push options ($PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push \
+ --porcelain \
+ -o issue=123 \
+ -o reviewer=user1 \
+ origin \
+ HEAD:refs/for/master/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ test_i18ngrep "fatal: the receiving end does not support push options" \
+ 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 ($PROTOCOL/porcelain)" '
+ 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 "proc-receive: push with options ($PROTOCOL/porcelain)" '
+ git -C workbench push \
+ --porcelain \
+ --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> ok refs/for/master/topic
+ 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 <URL/of/upstream.git>
+ * HEAD:refs/heads/next [new branch]
+ * HEAD:refs/for/master/topic [new reference]
+ Done
+ 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 ($PROTOCOL/porcelain)" '
+ git -C "$upstream" update-ref -d refs/heads/next
+'
diff --git a/t/t5411/test-0030-report-ok.sh b/t/t5411/test-0030-report-ok.sh
new file mode 100644
index 0000000..44c99d3
--- /dev/null
+++ b/t/t5411/test-0030-report-ok.sh
@@ -0,0 +1,35 @@
+test_expect_success "setup proc-receive hook (ok, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok 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: ok ($PROTOCOL)" '
+ 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> ok refs/for/master/topic
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ To <URL/of/upstream.git>
+ * [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
+'
diff --git a/t/t5411/test-0031-report-ok--porcelain.sh b/t/t5411/test-0031-report-ok--porcelain.sh
new file mode 100644
index 0000000..3223b26
--- /dev/null
+++ b/t/t5411/test-0031-report-ok--porcelain.sh
@@ -0,0 +1,36 @@
+test_expect_success "setup proc-receive hook (ok, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok 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: ok ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain 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> ok refs/for/master/topic
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ To <URL/of/upstream.git>
+ * HEAD:refs/for/master/topic [new reference]
+ Done
+ 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
+'
diff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh
new file mode 100644
index 0000000..b77b78c
--- /dev/null
+++ b/t/t5411/test-0032-report-with-options.sh
@@ -0,0 +1,256 @@
+test_expect_success "setup proc-receive hook (option without matching ok, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "option refname refs/pull/123/head" \
+ -r "option old-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report option without matching ok ($PROTOCOL)" '
+ 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> option refname refs/pull/123/head
+ remote: proc-receive> option old-oid <COMMIT-B>
+ remote: error: proc-receive reported "option" without a matching "ok/ng" directive
+ To <URL/of/upstream.git>
+ ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option refname, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/pull/123/head"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report option refname ($PROTOCOL)" '
+ 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> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/pull/123/head
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head
+ To <URL/of/upstream.git>
+ * [new reference] HEAD -> refs/pull/123/head
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option refname and forced-update, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/pull/123/head" \
+ -r "option forced-update"
+ EOF
+'
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report option refname and forced-update ($PROTOCOL)" '
+ 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> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/pull/123/head
+ remote: proc-receive> option forced-update
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head
+ To <URL/of/upstream.git>
+ * [new reference] HEAD -> refs/pull/123/head
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option refname and old-oid, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/pull/123/head" \
+ -r "option old-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report option refname and old-oid ($PROTOCOL)" '
+ 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> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/pull/123/head
+ remote: proc-receive> option old-oid <COMMIT-B>
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head
+ To <URL/of/upstream.git>
+ <OID-B>..<OID-A> HEAD -> refs/pull/123/head
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option old-oid, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option old-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report option old-oid ($PROTOCOL)" '
+ 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> ok refs/for/master/topic
+ remote: proc-receive> option old-oid <COMMIT-B>
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic
+ To <URL/of/upstream.git>
+ <OID-B>..<OID-A> HEAD -> refs/for/master/topic
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option old-oid and new-oid, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report option old-oid and new-oid ($PROTOCOL)" '
+ 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> ok refs/for/master/topic
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
+ To <URL/of/upstream.git>
+ <OID-A>..<OID-B> HEAD -> refs/for/master/topic
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (report with multiple rewrites, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/a/b/c/topic" \
+ -r "ok refs/for/next/topic" \
+ -r "option refname refs/pull/123/head" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/pull/124/head" \
+ -r "option old-oid $B" \
+ -r "option forced-update" \
+ -r "option new-oid $A"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report with multiple rewrites ($PROTOCOL)" '
+ git -C workbench push origin \
+ HEAD:refs/for/next/topic \
+ HEAD:refs/for/a/b/c/topic \
+ 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/next/topic
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ok refs/for/a/b/c/topic
+ remote: proc-receive> ok refs/for/next/topic
+ remote: proc-receive> option refname refs/pull/123/head
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/pull/124/head
+ remote: proc-receive> option old-oid <COMMIT-B>
+ remote: proc-receive> option forced-update
+ remote: proc-receive> option new-oid <COMMIT-A>
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head
+ To <URL/of/upstream.git>
+ * [new reference] HEAD -> refs/pull/123/head
+ * [new reference] HEAD -> refs/for/a/b/c/topic
+ + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)
+ 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
+'
diff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh
new file mode 100644
index 0000000..1fe352b
--- /dev/null
+++ b/t/t5411/test-0033-report-with-options--porcelain.sh
@@ -0,0 +1,265 @@
+test_expect_success "setup proc-receive hook (option without matching ok, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "option refname refs/pull/123/head" \
+ -r "option old-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report option without matching ok ($PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain 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> option refname refs/pull/123/head
+ remote: proc-receive> option old-oid <COMMIT-B>
+ remote: error: proc-receive reported "option" without a matching "ok/ng" directive
+ To <URL/of/upstream.git>
+ ! HEAD:refs/for/master/topic [remote rejected] (proc-receive failed to report status)
+ Done
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option refname, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/pull/123/head"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report option refname ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain 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> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/pull/123/head
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head
+ To <URL/of/upstream.git>
+ * HEAD:refs/pull/123/head [new reference]
+ Done
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option refname and forced-update, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/pull/123/head" \
+ -r "option forced-update"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report option refname and forced-update ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain 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> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/pull/123/head
+ remote: proc-receive> option forced-update
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head
+ To <URL/of/upstream.git>
+ * HEAD:refs/pull/123/head [new reference]
+ Done
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option refname and old-oid, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/pull/123/head" \
+ -r "option old-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report option refname and old-oid ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain 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> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/pull/123/head
+ remote: proc-receive> option old-oid <COMMIT-B>
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head
+ To <URL/of/upstream.git>
+ HEAD:refs/pull/123/head <OID-B>..<OID-A>
+ Done
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option old-oid, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option old-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report option old-oid ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain 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> ok refs/for/master/topic
+ remote: proc-receive> option old-oid <COMMIT-B>
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic
+ To <URL/of/upstream.git>
+ HEAD:refs/for/master/topic <OID-B>..<OID-A>
+ Done
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option old-oid and new-oid, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report option old-oid and new-oid ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain 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> ok refs/for/master/topic
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
+ To <URL/of/upstream.git>
+ HEAD:refs/for/master/topic <OID-A>..<OID-B>
+ Done
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (report with multiple rewrites, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/a/b/c/topic" \
+ -r "ok refs/for/next/topic" \
+ -r "option refname refs/pull/123/head" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/pull/124/head" \
+ -r "option old-oid $B" \
+ -r "option forced-update" \
+ -r "option new-oid $A"
+
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report with multiple rewrites ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain origin \
+ HEAD:refs/for/next/topic \
+ HEAD:refs/for/a/b/c/topic \
+ 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/next/topic
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive> ok refs/for/a/b/c/topic
+ remote: proc-receive> ok refs/for/next/topic
+ remote: proc-receive> option refname refs/pull/123/head
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/pull/124/head
+ remote: proc-receive> option old-oid <COMMIT-B>
+ remote: proc-receive> option forced-update
+ remote: proc-receive> option new-oid <COMMIT-A>
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head
+ To <URL/of/upstream.git>
+ * HEAD:refs/pull/123/head [new reference]
+ * HEAD:refs/for/a/b/c/topic [new reference]
+ + HEAD:refs/pull/124/head <OID-B>...<OID-A> (forced update)
+ Done
+ 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
+'
diff --git a/t/t5411/test-0034-report-ft.sh b/t/t5411/test-0034-report-ft.sh
new file mode 100644
index 0000000..aca2b06
--- /dev/null
+++ b/t/t5411/test-0034-report-ft.sh
@@ -0,0 +1,44 @@
+test_expect_success "setup proc-receive hook (ft, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option fall-through"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic(B)
+test_expect_success "proc-receive: fall throught, let receive-pack to execute ($PROTOCOL)" '
+ git -C workbench push origin \
+ $B: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-B> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option fall-through
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic
+ To <URL/of/upstream.git>
+ * [new reference] <COMMIT-B> -> 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-B> refs/for/master/topic
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(A) refs/for/master/topic(A)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL)" '
+ git -C "$upstream" update-ref -d refs/for/master/topic
+'
diff --git a/t/t5411/test-0035-report-ft--porcelain.sh b/t/t5411/test-0035-report-ft--porcelain.sh
new file mode 100644
index 0000000..30ffffb
--- /dev/null
+++ b/t/t5411/test-0035-report-ft--porcelain.sh
@@ -0,0 +1,45 @@
+test_expect_success "setup proc-receive hook (fall-through, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option fall-through"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic(B)
+test_expect_success "proc-receive: fall throught, let receive-pack to execute ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain origin \
+ $B: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-B> refs/for/master/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option fall-through
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic
+ To <URL/of/upstream.git>
+ * <COMMIT-B>:refs/for/master/topic [new reference]
+ Done
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-B> refs/for/master/topic
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(A) refs/for/master/topic(A)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+ git -C "$upstream" update-ref -d refs/for/master/topic
+'
diff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh
new file mode 100644
index 0000000..73283d8
--- /dev/null
+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh
@@ -0,0 +1,227 @@
+test_expect_success "setup git config for remote-tracking of special refs" '
+ (
+ cd workbench &&
+ if ! git config --get-all remote.origin.fetch | grep refs/for/
+ then
+ git config --add remote.origin.fetch \
+ "+refs/for/*:refs/t/for/*" &&
+ git config --add remote.origin.fetch \
+ "+refs/pull/*:refs/t/pull/*" &&
+ git config --add remote.origin.fetch \
+ "+refs/changes/*:refs/t/changes/*"
+ fi
+ )
+'
+
+test_expect_success "setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/changes/24/124/1" \
+ -r "option old-oid $ZERO_OID" \
+ -r "option new-oid $A" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/changes/25/125/1" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic(A)
+test_expect_success "proc-receive: multiple rewrite for one ref, no refname for the 1st rewrite ($PROTOCOL)" '
+ 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> ok refs/for/master/topic
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/changes/24/124/1
+ remote: proc-receive> option old-oid <ZERO-OID>
+ remote: proc-receive> option new-oid <COMMIT-A>
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/changes/25/125/1
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1
+ To <URL/of/upstream.git>
+ <OID-A>..<OID-B> HEAD -> refs/for/master/topic
+ * [new reference] HEAD -> refs/changes/24/124/1
+ <OID-A>..<OID-B> HEAD -> refs/changes/25/125/1
+ 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 "proc-receive: check remote-tracking #1 ($PROTOCOL)" '
+ git -C workbench show-ref |
+ grep -v -e refs/remotes -e refs/heads -e refs/tags >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/t/changes/24/124/1
+ <COMMIT-B> refs/t/changes/25/125/1
+ <COMMIT-B> refs/t/for/master/topic
+ EOF
+ test_cmp expect actual &&
+ git -C workbench update-ref -d refs/t/for/master/topic &&
+ git -C workbench update-ref -d refs/t/changes/24/124/1 &&
+ git -C workbench update-ref -d refs/t/changes/25/125/1
+'
+
+test_expect_success "setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/changes/24/124/1" \
+ -r "option old-oid $ZERO_OID" \
+ -r "option new-oid $A" \
+ -r "ok refs/for/master/topic" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/changes/25/125/1" \
+ -r "option old-oid $B" \
+ -r "option new-oid $A" \
+ -r "option forced-update"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic(A)
+test_expect_success "proc-receive: multiple rewrites for one ref, no refname for the 2nd rewrite ($PROTOCOL)" '
+ 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> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/changes/24/124/1
+ remote: proc-receive> option old-oid <ZERO-OID>
+ remote: proc-receive> option new-oid <COMMIT-A>
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/changes/25/125/1
+ remote: proc-receive> option old-oid <COMMIT-B>
+ remote: proc-receive> option new-oid <COMMIT-A>
+ remote: proc-receive> option forced-update
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1
+ To <URL/of/upstream.git>
+ * [new reference] HEAD -> refs/changes/24/124/1
+ <OID-A>..<OID-B> HEAD -> refs/for/master/topic
+ + <OID-B>...<OID-A> HEAD -> refs/changes/25/125/1 (forced update)
+ 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 "proc-receive: check remote-tracking #2 ($PROTOCOL)" '
+ git -C workbench show-ref |
+ grep -v -e refs/remotes -e refs/heads -e refs/tags >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/t/changes/24/124/1
+ <COMMIT-A> refs/t/changes/25/125/1
+ <COMMIT-B> refs/t/for/master/topic
+ EOF
+ test_cmp expect actual &&
+ git -C workbench update-ref -d refs/t/for/master/topic &&
+ git -C workbench update-ref -d refs/t/changes/24/124/1 &&
+ git -C workbench update-ref -d refs/t/changes/25/125/1
+'
+
+test_expect_success "setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/changes/23/123/1" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/changes/24/124/2" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic(A)
+test_expect_success "proc-receive: multiple rewrites for one ref ($PROTOCOL)" '
+ 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> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/changes/23/123/1
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/changes/24/124/2
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2
+ To <URL/of/upstream.git>
+ * [new reference] HEAD -> refs/changes/23/123/1
+ <OID-A>..<OID-B> HEAD -> refs/changes/24/124/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 "proc-receive: check remote-tracking #3 ($PROTOCOL)" '
+ git -C workbench show-ref |
+ grep -v -e refs/remotes -e refs/heads -e refs/tags >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-A> refs/t/changes/23/123/1
+ <COMMIT-B> refs/t/changes/24/124/2
+ EOF
+ test_cmp expect actual &&
+ git -C workbench update-ref -d refs/t/changes/24/124/1 &&
+ git -C workbench update-ref -d refs/t/changes/25/125/2
+'
diff --git a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh
new file mode 100644
index 0000000..77b5b22
--- /dev/null
+++ b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh
@@ -0,0 +1,172 @@
+test_expect_success "setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/changes/24/124/1" \
+ -r "option old-oid $ZERO_OID" \
+ -r "option new-oid $A" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/changes/25/125/1" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic(A)
+test_expect_success "proc-receive: multiple rewrite for one ref, no refname for the 1st rewrite ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain 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> ok refs/for/master/topic
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/changes/24/124/1
+ remote: proc-receive> option old-oid <ZERO-OID>
+ remote: proc-receive> option new-oid <COMMIT-A>
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/changes/25/125/1
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1
+ To <URL/of/upstream.git>
+ HEAD:refs/for/master/topic <OID-A>..<OID-B>
+ * HEAD:refs/changes/24/124/1 [new reference]
+ HEAD:refs/changes/25/125/1 <OID-A>..<OID-B>
+ Done
+ 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 (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/changes/24/124/1" \
+ -r "option old-oid $ZERO_OID" \
+ -r "option new-oid $A" \
+ -r "ok refs/for/master/topic" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/changes/25/125/1" \
+ -r "option old-oid $B" \
+ -r "option new-oid $A" \
+ -r "option forced-update"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic(A)
+test_expect_success "proc-receive: multiple rewrites for one ref, no refname for the 2nd rewrite ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain 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> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/changes/24/124/1
+ remote: proc-receive> option old-oid <ZERO-OID>
+ remote: proc-receive> option new-oid <COMMIT-A>
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/changes/25/125/1
+ remote: proc-receive> option old-oid <COMMIT-B>
+ remote: proc-receive> option new-oid <COMMIT-A>
+ remote: proc-receive> option forced-update
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1
+ To <URL/of/upstream.git>
+ * HEAD:refs/changes/24/124/1 [new reference]
+ HEAD:refs/for/master/topic <OID-A>..<OID-B>
+ + HEAD:refs/changes/25/125/1 <OID-B>...<OID-A> (forced update)
+ Done
+ 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 (multiple rewrites for one ref, $PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/changes/23/123/1" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/changes/24/124/2" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : refs/for/master/topic(A)
+test_expect_success "proc-receive: multiple rewrites for one ref ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain 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> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/changes/23/123/1
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/changes/24/124/2
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: # post-receive hook
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2
+ To <URL/of/upstream.git>
+ * HEAD:refs/changes/23/123/1 [new reference]
+ HEAD:refs/changes/24/124/2 <OID-A>..<OID-B>
+ Done
+ 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
+'
diff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh
new file mode 100644
index 0000000..a74a2cb
--- /dev/null
+++ b/t/t5411/test-0038-report-mixed-refs.sh
@@ -0,0 +1,89 @@
+test_expect_success "setup proc-receive hook ($PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/next/topic2" \
+ -r "ng refs/for/next/topic1 fail to call Web API" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/for/master/topic" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : (B) bar(A) baz(A) refs/for/next/topic(A) foo(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report update of mixed refs ($PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ $B:refs/heads/master \
+ HEAD:refs/heads/bar \
+ HEAD:refs/heads/baz \
+ HEAD:refs/for/next/topic2 \
+ HEAD:refs/for/next/topic1 \
+ HEAD:refs/heads/foo \
+ HEAD:refs/for/master/topic \
+ HEAD:refs/for/next/topic3 \
+ >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/heads/bar
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3
+ remote: proc-receive> ok refs/for/next/topic2
+ remote: proc-receive> ng refs/for/next/topic1 fail to call Web API
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/for/master/topic
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
+ To <URL/of/upstream.git>
+ <OID-A>..<OID-B> <COMMIT-B> -> master
+ * [new branch] HEAD -> bar
+ * [new branch] HEAD -> baz
+ * [new reference] HEAD -> refs/for/next/topic2
+ * [new branch] HEAD -> foo
+ <OID-A>..<OID-B> HEAD -> refs/for/master/topic
+ ! [remote rejected] HEAD -> refs/for/next/topic1 (fail to call Web API)
+ ! [remote rejected] HEAD -> refs/for/next/topic3 (proc-receive failed to report status)
+ 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/bar
+ <COMMIT-A> refs/heads/baz
+ <COMMIT-A> refs/heads/foo
+ <COMMIT-B> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(B) foo(A) bar(A)) baz(A)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL)" '
+ (
+ cd "$upstream" &&
+ git update-ref refs/heads/master $A &&
+ git update-ref -d refs/heads/foo &&
+ git update-ref -d refs/heads/bar &&
+ git update-ref -d refs/heads/baz
+ )
+'
diff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh
new file mode 100644
index 0000000..e4baa13
--- /dev/null
+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh
@@ -0,0 +1,91 @@
+test_expect_success "setup proc-receive hook ($PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/next/topic2" \
+ -r "ng refs/for/next/topic1 fail to call Web API" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/for/master/topic" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : (B) bar(A) baz(A) refs/for/next/topic(A) foo(A) refs/for/master/topic(A)
+test_expect_success "proc-receive: report update of mixed refs ($PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain origin \
+ $B:refs/heads/master \
+ HEAD:refs/heads/bar \
+ HEAD:refs/heads/baz \
+ HEAD:refs/for/next/topic2 \
+ HEAD:refs/for/next/topic1 \
+ HEAD:refs/heads/foo \
+ HEAD:refs/for/master/topic \
+ HEAD:refs/for/next/topic3 \
+ >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/heads/bar
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3
+ remote: # proc-receive hook
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3
+ remote: proc-receive> ok refs/for/next/topic2
+ remote: proc-receive> ng refs/for/next/topic1 fail to call Web API
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/for/master/topic
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic
+ To <URL/of/upstream.git>
+ <COMMIT-B>:refs/heads/master <OID-A>..<OID-B>
+ * HEAD:refs/heads/bar [new branch]
+ * HEAD:refs/heads/baz [new branch]
+ * HEAD:refs/for/next/topic2 [new reference]
+ * HEAD:refs/heads/foo [new branch]
+ HEAD:refs/for/master/topic <OID-A>..<OID-B>
+ ! HEAD:refs/for/next/topic1 [remote rejected] (fail to call Web API)
+ ! HEAD:refs/for/next/topic3 [remote rejected] (proc-receive failed to report status)
+ Done
+ 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/bar
+ <COMMIT-A> refs/heads/baz
+ <COMMIT-A> refs/heads/foo
+ <COMMIT-B> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(B) foo(A) bar(A)) baz(A)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+ (
+ cd "$upstream" &&
+ git update-ref refs/heads/master $A &&
+ git update-ref -d refs/heads/foo &&
+ git update-ref -d refs/heads/bar &&
+ git update-ref -d refs/heads/baz
+ )
+
+'
diff --git a/t/t5411/test-0040-process-all-refs.sh b/t/t5411/test-0040-process-all-refs.sh
new file mode 100644
index 0000000..b07c999
--- /dev/null
+++ b/t/t5411/test-0040-process-all-refs.sh
@@ -0,0 +1,113 @@
+test_expect_success "config receive.procReceiveRefs = refs ($PROTOCOL)" '
+ git -C "$upstream" config --unset-all receive.procReceiveRefs &&
+ git -C "$upstream" config --add receive.procReceiveRefs refs
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "setup upstream branches ($PROTOCOL)" '
+ (
+ cd "$upstream" &&
+ git update-ref refs/heads/master $B &&
+ git update-ref refs/heads/foo $A &&
+ git update-ref refs/heads/bar $A &&
+ git update-ref refs/heads/baz $A
+ )
+
+'
+
+test_expect_success "setup proc-receive hook ($PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/heads/master" \
+ -r "option fall-through" \
+ -r "ok refs/heads/foo" \
+ -r "option fall-through" \
+ -r "ok refs/heads/bar" \
+ -r "option fall-through" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/pull/123/head" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B" \
+ -r "ok refs/for/next/topic" \
+ -r "option refname refs/pull/124/head" \
+ -r "option old-oid $B" \
+ -r "option new-oid $A" \
+ -r "option forced-update"
+ EOF
+'
+
+# Refs of upstream : master(B) foo(A) bar(A)) baz(A)
+# Refs of workbench: master(A) tags/v123
+# git push -f : master(A) (NULL) (B) refs/for/master/topic(A) refs/for/next/topic(A)
+test_expect_success "proc-receive: process all refs ($PROTOCOL)" '
+ git -C workbench push -f origin \
+ HEAD:refs/heads/master \
+ :refs/heads/foo \
+ $B:refs/heads/bar \
+ HEAD:refs/for/master/topic \
+ HEAD:refs/for/next/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/bar
+ remote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo
+ remote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar
+ remote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo
+ remote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+ remote: proc-receive> ok refs/heads/master
+ remote: proc-receive> option fall-through
+ remote: proc-receive> ok refs/heads/foo
+ remote: proc-receive> option fall-through
+ remote: proc-receive> ok refs/heads/bar
+ remote: proc-receive> option fall-through
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/pull/123/head
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: proc-receive> ok refs/for/next/topic
+ remote: proc-receive> option refname refs/pull/124/head
+ remote: proc-receive> option old-oid <COMMIT-B>
+ remote: proc-receive> option new-oid <COMMIT-A>
+ remote: proc-receive> option forced-update
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar
+ remote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head
+ To <URL/of/upstream.git>
+ <OID-A>..<OID-B> <COMMIT-B> -> bar
+ - [deleted] foo
+ + <OID-B>...<OID-A> HEAD -> master (forced update)
+ <OID-A>..<OID-B> HEAD -> refs/pull/123/head
+ + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update)
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-B> refs/heads/bar
+ <COMMIT-A> refs/heads/baz
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(A) bar(A) baz(B)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL)" '
+ (
+ cd "$upstream" &&
+ git update-ref -d refs/heads/bar &&
+ git update-ref -d refs/heads/baz
+ )
+'
diff --git a/t/t5411/test-0041-process-all-refs--porcelain.sh b/t/t5411/test-0041-process-all-refs--porcelain.sh
new file mode 100644
index 0000000..0dd9824
--- /dev/null
+++ b/t/t5411/test-0041-process-all-refs--porcelain.sh
@@ -0,0 +1,114 @@
+test_expect_success "config receive.procReceiveRefs = refs ($PROTOCOL/porcelain)" '
+ git -C "$upstream" config --unset-all receive.procReceiveRefs &&
+ git -C "$upstream" config --add receive.procReceiveRefs refs
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "setup upstream branches ($PROTOCOL/porcelain)" '
+ (
+ cd "$upstream" &&
+ git update-ref refs/heads/master $B &&
+ git update-ref refs/heads/foo $A &&
+ git update-ref refs/heads/bar $A &&
+ git update-ref refs/heads/baz $A
+ )
+
+'
+
+test_expect_success "setup proc-receive hook ($PROTOCOL/porcelain)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/heads/master" \
+ -r "option fall-through" \
+ -r "ok refs/heads/foo" \
+ -r "option fall-through" \
+ -r "ok refs/heads/bar" \
+ -r "option fall-through" \
+ -r "ok refs/for/master/topic" \
+ -r "option refname refs/pull/123/head" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B" \
+ -r "ok refs/for/next/topic" \
+ -r "option refname refs/pull/124/head" \
+ -r "option old-oid $B" \
+ -r "option new-oid $A" \
+ -r "option forced-update"
+ EOF
+'
+
+# Refs of upstream : master(B) foo(A) bar(A)) baz(A)
+# Refs of workbench: master(A) tags/v123
+# git push -f : master(A) (NULL) (B) refs/for/master/topic(A) refs/for/next/topic(A)
+test_expect_success "proc-receive: process all refs ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain -f origin \
+ HEAD:refs/heads/master \
+ :refs/heads/foo \
+ $B:refs/heads/bar \
+ HEAD:refs/for/master/topic \
+ HEAD:refs/for/next/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/bar
+ remote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo
+ remote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+ remote: # proc-receive hook
+ remote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar
+ remote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo
+ remote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+ remote: proc-receive> ok refs/heads/master
+ remote: proc-receive> option fall-through
+ remote: proc-receive> ok refs/heads/foo
+ remote: proc-receive> option fall-through
+ remote: proc-receive> ok refs/heads/bar
+ remote: proc-receive> option fall-through
+ remote: proc-receive> ok refs/for/master/topic
+ remote: proc-receive> option refname refs/pull/123/head
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: proc-receive> ok refs/for/next/topic
+ remote: proc-receive> option refname refs/pull/124/head
+ remote: proc-receive> option old-oid <COMMIT-B>
+ remote: proc-receive> option new-oid <COMMIT-A>
+ remote: proc-receive> option forced-update
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar
+ remote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head
+ remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head
+ To <URL/of/upstream.git>
+ <COMMIT-B>:refs/heads/bar <OID-A>..<OID-B>
+ - :refs/heads/foo [deleted]
+ + HEAD:refs/heads/master <OID-B>...<OID-A> (forced update)
+ HEAD:refs/pull/123/head <OID-A>..<OID-B>
+ + HEAD:refs/pull/124/head <OID-B>...<OID-A> (forced update)
+ Done
+ EOF
+ test_cmp expect actual &&
+ git -C "$upstream" show-ref >out &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ <COMMIT-B> refs/heads/bar
+ <COMMIT-A> refs/heads/baz
+ <COMMIT-A> refs/heads/master
+ EOF
+ test_cmp expect actual
+'
+
+# Refs of upstream : master(A) bar(A) baz(B)
+# Refs of workbench: master(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+ (
+ cd "$upstream" &&
+ git update-ref -d refs/heads/bar &&
+ git update-ref -d refs/heads/baz
+ )
+'
diff --git a/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh b/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh
new file mode 100644
index 0000000..c22849c
--- /dev/null
+++ b/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh
@@ -0,0 +1,135 @@
+test_expect_success "config receive.procReceiveRefs with modifiers ($PROTOCOL)" '
+ (
+ cd "$upstream" &&
+ git config --unset-all receive.procReceiveRefs &&
+ git config --add receive.procReceiveRefs m:refs/heads/master &&
+ git config --add receive.procReceiveRefs ad:refs/heads &&
+ git config --add receive.procReceiveRefs "a!:refs/heads"
+ )
+'
+
+test_expect_success "setup proc-receive hook ($PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/heads/master" \
+ -r "option refname refs/pull/123/head" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B" \
+ -r "ok refs/tags/v123 " \
+ -r "option refname refs/pull/124/head"
+ EOF
+'
+
+# Refs of upstream : master(A)
+# Refs of workbench: master(A) tags/v123
+# git push : master(B) tags/v123
+test_expect_success "proc-receive: update branch and new tag ($PROTOCOL)" '
+ git -C workbench push origin \
+ $B:refs/heads/master \
+ v123 >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> <TAG-v123> refs/tags/v123
+ remote: # proc-receive hook
+ remote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/master
+ remote: proc-receive< <ZERO-OID> <TAG-v123> refs/tags/v123
+ remote: proc-receive> ok refs/heads/master
+ remote: proc-receive> option refname refs/pull/123/head
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <COMMIT-B>
+ remote: proc-receive> ok refs/tags/v123
+ remote: proc-receive> option refname refs/pull/124/head
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head
+ remote: post-receive< <ZERO-OID> <TAG-v123> refs/pull/124/head
+ To <URL/of/upstream.git>
+ <OID-A>..<OID-B> <COMMIT-B> -> refs/pull/123/head
+ * [new reference] v123 -> refs/pull/124/head
+ 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
+test_expect_success "setup upstream: create tags/v123 ($PROTOCOL)" '
+ git -C "$upstream" update-ref refs/heads/topic $A &&
+ git -C "$upstream" update-ref refs/tags/v123 $TAG &&
+ 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/topic
+ <TAG-v123> refs/tags/v123
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook ($PROTOCOL)" '
+ write_script "$upstream/hooks/proc-receive" <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/heads/master" \
+ -r "option refname refs/pull/123/head" \
+ -r "option old-oid $A" \
+ -r "option new-oid $ZERO_OID" \
+ -r "ok refs/heads/next" \
+ -r "option refname refs/pull/124/head" \
+ -r "option new-oid $A"
+ EOF
+'
+
+# Refs of upstream : master(A) topic(A) tags/v123
+# Refs of workbench: master(A) tags/v123
+# git push : NULL topic(B) NULL next(A)
+test_expect_success "proc-receive: create/delete branch, and delete tag ($PROTOCOL)" '
+ git -C workbench push origin \
+ :refs/heads/master \
+ $B:refs/heads/topic \
+ $A:refs/heads/next \
+ :refs/tags/v123 >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ cat >expect <<-EOF &&
+ remote: # pre-receive hook
+ remote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/master
+ remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/topic
+ remote: pre-receive< <TAG-v123> <ZERO-OID> refs/tags/v123
+ remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ remote: # proc-receive hook
+ remote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/master
+ remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
+ remote: proc-receive> ok refs/heads/master
+ remote: proc-receive> option refname refs/pull/123/head
+ remote: proc-receive> option old-oid <COMMIT-A>
+ remote: proc-receive> option new-oid <ZERO-OID>
+ remote: proc-receive> ok refs/heads/next
+ remote: proc-receive> option refname refs/pull/124/head
+ remote: proc-receive> option new-oid <COMMIT-A>
+ remote: # post-receive hook
+ remote: post-receive< <COMMIT-A> <ZERO-OID> refs/pull/123/head
+ remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/topic
+ remote: post-receive< <TAG-v123> <ZERO-OID> refs/tags/v123
+ remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/124/head
+ To <URL/of/upstream.git>
+ - [deleted] refs/pull/123/head
+ <OID-A>..<OID-B> <COMMIT-B> -> topic
+ - [deleted] v123
+ * [new reference] <COMMIT-A> -> refs/pull/124/head
+ 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-B> refs/heads/topic
+ EOF
+ test_cmp expect actual
+'
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 36ad20a..d11382f 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -1039,7 +1039,7 @@ test_force_fetch_tag "annotated tag" "-f -a -m'tag message'"
test_expect_success 'push --porcelain' '
mk_empty testrepo &&
echo >.git/foo "To testrepo" &&
- echo >>.git/foo "* refs/heads/master:refs/remotes/origin/master [new branch]" &&
+ echo >>.git/foo "* refs/heads/master:refs/remotes/origin/master [new reference]" &&
echo >>.git/foo "Done" &&
git push >.git/bar --porcelain testrepo refs/heads/master:refs/remotes/origin/master &&
(