summaryrefslogtreecommitdiffstats
path: root/t/t5411
diff options
context:
space:
mode:
Diffstat (limited to 't/t5411')
-rw-r--r--t/t5411/common-functions.sh73
-rw-r--r--t/t5411/once-0010-report-status-v1.sh91
-rw-r--r--t/t5411/test-0000-standard-git-push.sh134
-rw-r--r--t/t5411/test-0001-standard-git-push--porcelain.sh138
-rw-r--r--t/t5411/test-0002-pre-receive-declined.sh31
-rw-r--r--t/t5411/test-0003-pre-receive-declined--porcelain.sh32
-rw-r--r--t/t5411/test-0010-proc-receive-settings.sh7
-rw-r--r--t/t5411/test-0011-no-hook-error.sh60
-rw-r--r--t/t5411/test-0012-no-hook-error--porcelain.sh62
-rw-r--r--t/t5411/test-0013-bad-protocol.sh302
-rw-r--r--t/t5411/test-0014-bad-protocol--porcelain.sh304
-rw-r--r--t/t5411/test-0020-report-ng.sh63
-rw-r--r--t/t5411/test-0021-report-ng--porcelain.sh65
-rw-r--r--t/t5411/test-0022-report-unexpect-ref.sh43
-rw-r--r--t/t5411/test-0023-report-unexpect-ref--porcelain.sh44
-rw-r--r--t/t5411/test-0024-report-unknown-ref.sh32
-rw-r--r--t/t5411/test-0025-report-unknown-ref--porcelain.sh33
-rw-r--r--t/t5411/test-0026-push-options.sh133
-rw-r--r--t/t5411/test-0027-push-options--porcelain.sh138
-rw-r--r--t/t5411/test-0030-report-ok.sh33
-rw-r--r--t/t5411/test-0031-report-ok--porcelain.sh34
-rw-r--r--t/t5411/test-0032-report-with-options.sh253
-rw-r--r--t/t5411/test-0033-report-with-options--porcelain.sh262
-rw-r--r--t/t5411/test-0034-report-ft.sh42
-rw-r--r--t/t5411/test-0035-report-ft--porcelain.sh43
-rw-r--r--t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh221
-rw-r--r--t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh166
-rw-r--r--t/t5411/test-0038-report-mixed-refs.sh87
-rw-r--r--t/t5411/test-0039-report-mixed-refs--porcelain.sh89
-rw-r--r--t/t5411/test-0040-process-all-refs.sh111
-rw-r--r--t/t5411/test-0041-process-all-refs--porcelain.sh112
-rw-r--r--t/t5411/test-0050-proc-receive-refs-with-modifiers.sh129
32 files changed, 3367 insertions, 0 deletions
diff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh
new file mode 100644
index 0000000..3c74778
--- /dev/null
+++ b/t/t5411/common-functions.sh
@@ -0,0 +1,73 @@
+# 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" && test -d "$repo" ||
+ error "Repository $repo does not exist."
+ shift &&
+ while test $# -gt 0
+ do
+ name=$1 &&
+ shift &&
+ test_commit -C "$repo" --no-tag "$name" &&
+ eval $name=$(git -C "$repo" rev-parse HEAD)
+ done
+}
+
+get_abbrev_oid () {
+ oid=$1 &&
+ suffix=${oid#???????} &&
+ oid=${oid%$suffix} &&
+ if test -n "$oid"
+ then
+ echo "$oid"
+ else
+ echo "undefined-oid"
+ fi
+}
+
+# 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 changes of the commit ID (full or abbrev.)
+# 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. The emitted human-readable errors are
+# redundant to the more machine-readable output the tests already assert.
+make_user_friendly_and_stable_output () {
+ sed \
+ -e "s/'/\"/g" \
+ -e "s/$(get_abbrev_oid $A)[0-9a-f]*/<COMMIT-A>/g" \
+ -e "s/$(get_abbrev_oid $B)[0-9a-f]*/<COMMIT-B>/g" \
+ -e "s/$(get_abbrev_oid $TAG)[0-9a-f]*/<TAG-v123>/g" \
+ -e "s/$ZERO_OID/<ZERO-OID>/g" \
+ -e "s#To $URL_PREFIX/upstream.git#To <URL/of/upstream.git>#" \
+ -e "/^error: / d"
+}
+
+filter_out_user_friendly_and_stable_output () {
+ make_user_friendly_and_stable_output |
+ sed -n ${1+"$@"}
+}
+
+format_and_save_expect () {
+ sed -e 's/^> //' -e 's/Z$//' >expect
+}
+
+test_cmp_refs () {
+ indir=
+ if test "$1" = "-C"
+ then
+ shift
+ indir="$1"
+ shift
+ fi
+ indir=${indir:+"$indir"/}
+ cat >show-ref.expect &&
+ git ${indir:+ -C "$indir"} show-ref >show-ref.pristine &&
+ make_user_friendly_and_stable_output <show-ref.pristine >show-ref.filtered &&
+ test_cmp show-ref.expect show-ref.filtered
+}
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..f9ffb01
--- /dev/null
+++ b/t/t5411/once-0010-report-status-v1.sh
@@ -0,0 +1,91 @@
+test_expect_success "setup receive.procReceiveRefs" '
+ git -C "$upstream" config --add receive.procReceiveRefs refs/for
+'
+
+test_expect_success "setup proc-receive hook" '
+ test_hook -C "$upstream" --clobber proc-receive <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/main/topic1" \
+ -r "option fall-through" \
+ -r "ok refs/for/main/topic2" \
+ -r "option refname refs/for/changes/23/123/1" \
+ -r "option new-oid $A" \
+ -r "ok refs/for/main/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 : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : (B) refs/for/main/topic1(A) foo(A) refs/for/next/topic(A) refs/for/main/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/main\0report-status\n" \
+ $A $B | packetize_raw
+ else
+ printf "%s %s refs/heads/main\0report-status object-format=$GIT_DEFAULT_HASH\n" \
+ $A $B | packetize_raw
+ fi &&
+ printf "%s %s refs/for/main/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/main/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/main
+ pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/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/main/topic2
+ # proc-receive hook
+ proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic1
+ proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic
+ proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic2
+ proc-receive> ok refs/for/main/topic1
+ proc-receive> option fall-through
+ proc-receive> ok refs/for/main/topic2
+ proc-receive> option refname refs/for/changes/23/123/1
+ proc-receive> option new-oid <COMMIT-A>
+ proc-receive> ok refs/for/main/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
+ 0017ok refs/heads/main
+ 001cok refs/for/main/topic1
+ 0016ok refs/heads/foo
+ 0033ng refs/for/next/topic target branch not exist
+ 001cok refs/for/main/topic2
+ 0000# post-receive hook
+ post-receive< <COMMIT-A> <COMMIT-B> refs/heads/main
+ post-receive< <ZERO-OID> <COMMIT-A> refs/for/main/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 &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/for/main/topic1
+ <COMMIT-A> refs/heads/foo
+ <COMMIT-B> refs/heads/main
+ EOF
+'
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..ce64bb6
--- /dev/null
+++ b/t/t5411/test-0000-standard-git-push.sh
@@ -0,0 +1,134 @@
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git-push : main(B) next(A)
+test_expect_success "git-push ($PROTOCOL)" '
+ git -C workbench push origin \
+ $B:refs/heads/main \
+ HEAD:refs/heads/next \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/main Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next Z
+ > To <URL/of/upstream.git>
+ > <COMMIT-A>..<COMMIT-B> <COMMIT-B> -> main
+ > * [new branch] HEAD -> next
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-B> refs/heads/main
+ <COMMIT-A> refs/heads/next
+ EOF
+'
+
+# Refs of upstream : main(B) next(A)
+# Refs of workbench: main(A) tags/v123
+# git-push --atomic: main(A) next(B)
+test_expect_success "git-push --atomic ($PROTOCOL)" '
+ test_must_fail git -C workbench push --atomic origin \
+ main \
+ $B:refs/heads/next \
+ >out-$test_count 2>&1 &&
+ filter_out_user_friendly_and_stable_output \
+ -e "/^To / { p; }" \
+ -e "/^ ! / { p; }" \
+ <out-$test_count >actual &&
+ format_and_save_expect <<-EOF &&
+ > To <URL/of/upstream.git>
+ > ! [rejected] main -> main (non-fast-forward)
+ > ! [rejected] <COMMIT-B> -> next (atomic push failed)
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-B> refs/heads/main
+ <COMMIT-A> refs/heads/next
+ EOF
+'
+
+# Refs of upstream : main(B) next(A)
+# Refs of workbench: main(A) tags/v123
+# git-push : main(A) next(B)
+test_expect_success "non-fast-forward git-push ($PROTOCOL)" '
+ test_must_fail git \
+ -C workbench \
+ -c advice.pushUpdateRejected=false \
+ push origin \
+ main \
+ $B:refs/heads/next \
+ >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/next Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/next Z
+ > To <URL/of/upstream.git>
+ > <COMMIT-A>..<COMMIT-B> <COMMIT-B> -> next
+ > ! [rejected] main -> main (non-fast-forward)
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-B> refs/heads/main
+ <COMMIT-B> refs/heads/next
+ EOF
+'
+
+# Refs of upstream : main(B) next(B)
+# Refs of workbench: main(A) tags/v123
+# git-push -f : main(A) NULL tags/v123 refs/review/main/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 \
+ main \
+ main:refs/review/main/topic \
+ HEAD:refs/heads/a/b/c \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/main Z
+ > remote: pre-receive< <COMMIT-B> <ZERO-OID> refs/heads/next Z
+ > remote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123 Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/main/topic Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/main Z
+ > remote: post-receive< <COMMIT-B> <ZERO-OID> refs/heads/next Z
+ > remote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123 Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/main/topic Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c Z
+ > To <URL/of/upstream.git>
+ > + <COMMIT-B>...<COMMIT-A> main -> main (forced update)
+ > - [deleted] next
+ > * [new tag] v123 -> v123
+ > * [new reference] main -> refs/review/main/topic
+ > * [new branch] HEAD -> a/b/c
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/a/b/c
+ <COMMIT-A> refs/heads/main
+ <COMMIT-A> refs/review/main/topic
+ <TAG-v123> refs/tags/v123
+ EOF
+'
+
+# Refs of upstream : main(A) tags/v123 refs/review/main/topic(A) a/b/c(A)
+# Refs of workbench: main(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL)" '
+ (
+ cd "$upstream" &&
+ git update-ref -d refs/review/main/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..373ec3d
--- /dev/null
+++ b/t/t5411/test-0001-standard-git-push--porcelain.sh
@@ -0,0 +1,138 @@
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git-push : main(B) next(A)
+test_expect_success "git-push ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain origin \
+ $B:refs/heads/main \
+ HEAD:refs/heads/next \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/main Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next Z
+ > To <URL/of/upstream.git>
+ > <COMMIT-B>:refs/heads/main <COMMIT-A>..<COMMIT-B>
+ > * HEAD:refs/heads/next [new branch]
+ > Done
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-B> refs/heads/main
+ <COMMIT-A> refs/heads/next
+ EOF
+'
+
+# Refs of upstream : main(B) next(A)
+# Refs of workbench: main(A) tags/v123
+# git-push --atomic: main(A) next(B)
+test_expect_success "git-push --atomic ($PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --atomic --porcelain origin \
+ main \
+ $B:refs/heads/next \
+ >out-$test_count 2>&1 &&
+ filter_out_user_friendly_and_stable_output \
+ -e "s/^# GETTEXT POISON #//" \
+ -e "/^To / { p; }" \
+ -e "/^!/ { p; }" \
+ <out-$test_count >actual &&
+ format_and_save_expect <<-EOF &&
+ > To <URL/of/upstream.git>
+ > ! refs/heads/main:refs/heads/main [rejected] (non-fast-forward)
+ > ! <COMMIT-B>:refs/heads/next [rejected] (atomic push failed)
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-B> refs/heads/main
+ <COMMIT-A> refs/heads/next
+ EOF
+'
+
+# Refs of upstream : main(B) next(A)
+# Refs of workbench: main(A) tags/v123
+# git-push : main(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 \
+ main \
+ $B:refs/heads/next \
+ >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/next Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/next Z
+ > To <URL/of/upstream.git>
+ > <COMMIT-B>:refs/heads/next <COMMIT-A>..<COMMIT-B>
+ > ! refs/heads/main:refs/heads/main [rejected] (non-fast-forward)
+ > Done
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-B> refs/heads/main
+ <COMMIT-B> refs/heads/next
+ EOF
+'
+
+# Refs of upstream : main(B) next(B)
+# Refs of workbench: main(A) tags/v123
+# git-push -f : main(A) NULL tags/v123 refs/review/main/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 \
+ main \
+ main:refs/review/main/topic \
+ HEAD:refs/heads/a/b/c \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/main Z
+ > remote: pre-receive< <COMMIT-B> <ZERO-OID> refs/heads/next Z
+ > remote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123 Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/main/topic Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/main Z
+ > remote: post-receive< <COMMIT-B> <ZERO-OID> refs/heads/next Z
+ > remote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123 Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/main/topic Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c Z
+ > To <URL/of/upstream.git>
+ > + refs/heads/main:refs/heads/main <COMMIT-B>...<COMMIT-A> (forced update)
+ > - :refs/heads/next [deleted]
+ > * refs/tags/v123:refs/tags/v123 [new tag]
+ > * refs/heads/main:refs/review/main/topic [new reference]
+ > * HEAD:refs/heads/a/b/c [new branch]
+ > Done
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/a/b/c
+ <COMMIT-A> refs/heads/main
+ <COMMIT-A> refs/review/main/topic
+ <TAG-v123> refs/tags/v123
+ EOF
+'
+
+# Refs of upstream : main(A) tags/v123 refs/review/main/topic(A) a/b/c(A)
+# Refs of workbench: main(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+ (
+ cd "$upstream" &&
+ git update-ref -d refs/review/main/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..98a9d13
--- /dev/null
+++ b/t/t5411/test-0002-pre-receive-declined.sh
@@ -0,0 +1,31 @@
+test_expect_success "setup pre-receive hook ($PROTOCOL)" '
+ mv "$upstream/hooks/pre-receive" "$upstream/hooks/pre-receive.ok" &&
+ test_hook -C "$upstream" --clobber pre-receive <<-\EOF
+ exit 1
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git-push : main(B) next(A)
+test_expect_success "git-push is declined ($PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ $B:refs/heads/main \
+ HEAD:refs/heads/next \
+ >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ ! [remote rejected] <COMMIT-B> -> main (pre-receive hook declined)
+ ! [remote rejected] HEAD -> next (pre-receive hook declined)
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-\EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+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..67ca6dc
--- /dev/null
+++ b/t/t5411/test-0003-pre-receive-declined--porcelain.sh
@@ -0,0 +1,32 @@
+test_expect_success "setup pre-receive hook ($PROTOCOL/porcelain)" '
+ mv "$upstream/hooks/pre-receive" "$upstream/hooks/pre-receive.ok" &&
+ test_hook -C "$upstream" --clobber pre-receive <<-\EOF
+ exit 1
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git-push : main(B) next(A)
+test_expect_success "git-push is declined ($PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain origin \
+ $B:refs/heads/main \
+ HEAD:refs/heads/next \
+ >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ format_and_save_expect <<-EOF &&
+ > To <URL/of/upstream.git>
+ > ! <COMMIT-B>:refs/heads/main [remote rejected] (pre-receive hook declined)
+ > ! HEAD:refs/heads/next [remote rejected] (pre-receive hook declined)
+ Done
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+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..d35002b
--- /dev/null
+++ b/t/t5411/test-0011-no-hook-error.sh
@@ -0,0 +1,60 @@
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : next(A) refs/for/main/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/main/topic \
+ >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: error: cannot find hook "proc-receive" Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next Z
+ > To <URL/of/upstream.git>
+ > * [new branch] HEAD -> next
+ > ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ <COMMIT-A> refs/heads/next
+ EOF
+'
+
+# Refs of upstream : main(A) next(A)
+# Refs of workbench: main(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL)" '
+ git -C "$upstream" update-ref -d refs/heads/next
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push --atomic: (B) next(A) refs/for/main/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:main \
+ HEAD:next \
+ HEAD:refs/for/main/topic >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: error: cannot find hook "proc-receive" Z
+ > To <URL/of/upstream.git>
+ > ! [remote rejected] <COMMIT-B> -> main (fail to run proc-receive hook)
+ > ! [remote rejected] HEAD -> next (fail to run proc-receive hook)
+ > ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
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..04468b5
--- /dev/null
+++ b/t/t5411/test-0012-no-hook-error--porcelain.sh
@@ -0,0 +1,62 @@
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : next(A) refs/for/main/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/main/topic \
+ >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: error: cannot find hook "proc-receive" Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next Z
+ > To <URL/of/upstream.git>
+ > * HEAD:refs/heads/next [new branch]
+ > ! HEAD:refs/for/main/topic [remote rejected] (fail to run proc-receive hook)
+ Done
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ <COMMIT-A> refs/heads/next
+ EOF
+'
+
+# Refs of upstream : main(A) next(A)
+# Refs of workbench: main(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+ git -C "$upstream" update-ref -d refs/heads/next
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push --atomic: (B) next(A) refs/for/main/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:main \
+ HEAD:next \
+ HEAD:refs/for/main/topic >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: error: cannot find hook "proc-receive" Z
+ > To <URL/of/upstream.git>
+ > ! <COMMIT-B>:refs/heads/main [remote rejected] (fail to run proc-receive hook)
+ > ! HEAD:refs/heads/next [remote rejected] (fail to run proc-receive hook)
+ > ! HEAD:refs/for/main/topic [remote rejected] (fail to run proc-receive hook)
+ > Done
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
diff --git a/t/t5411/test-0013-bad-protocol.sh b/t/t5411/test-0013-bad-protocol.sh
new file mode 100644
index 0000000..8d22e17
--- /dev/null
+++ b/t/t5411/test-0013-bad-protocol.sh
@@ -0,0 +1,302 @@
+test_expect_success "setup proc-receive hook (unknown version, $PROTOCOL)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v --version 2
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/topic(A)
+test_expect_success "proc-receive: bad protocol (unknown version, $PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/main/topic \
+ >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+
+ # Check status report for git-push
+ sed -n \
+ -e "/^To / { p; }" \
+ -e "/^ ! / { p; }" \
+ <actual >actual-report &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ ! [remote rejected] HEAD -> refs/for/main/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 &&
+ format_and_save_expect <<-EOF &&
+ > remote: error: proc-receive version "2" is not supported Z
+ EOF
+ test_cmp expect actual-error &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+test_expect_success "setup proc-receive hook (hook --die-read-version, $PROTOCOL)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v --die-read-version
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/topic(A)
+test_expect_success "proc-receive: bad protocol (hook --die-read-version, $PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/main/topic \
+ >out-$test_count 2>&1 &&
+ filter_out_user_friendly_and_stable_output \
+ -e "/^To / { p; }" \
+ -e "/^ ! / { p; }" \
+ <out-$test_count >actual &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)
+ EOF
+ test_cmp expect actual &&
+ grep "remote: fatal: die with the --die-read-version option" out-$test_count &&
+ grep "remote: error: fail to negotiate version with proc-receive hook" out-$test_count &&
+
+ test_cmp_refs -C "$upstream" <<-\EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+test_expect_success "setup proc-receive hook (hook --die-write-version, $PROTOCOL)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v --die-write-version
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/topic(A)
+test_expect_success "proc-receive: bad protocol (hook --die-write-version, $PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/main/topic \
+ >out-$test_count 2>&1 &&
+ filter_out_user_friendly_and_stable_output \
+ -e "/^To / { p; }" \
+ -e "/^ ! / { p; }" \
+ <out-$test_count >actual &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)
+ EOF
+ test_cmp expect actual &&
+ grep "remote: fatal: die with the --die-write-version option" out-$test_count &&
+ grep "remote: error: fail to negotiate version with proc-receive hook" out-$test_count &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+test_expect_success "setup proc-receive hook (hook --die-read-commands, $PROTOCOL)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v --die-read-commands
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/topic(A)
+test_expect_success "proc-receive: bad protocol (hook --die-read-commands, $PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/main/topic \
+ >out-$test_count 2>&1 &&
+ filter_out_user_friendly_and_stable_output \
+ -e "/^To / { p; }" \
+ -e "/^ ! / { p; }" \
+ <out-$test_count >actual &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)
+ EOF
+ test_cmp expect actual &&
+ grep "remote: fatal: die with the --die-read-commands option" out-$test_count &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+test_expect_success "setup proc-receive hook (hook --die-read-push-options, $PROTOCOL)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v --die-read-push-options
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/topic(A)
+test_expect_success "proc-receive: bad protocol (hook --die-read-push-options, $PROTOCOL)" '
+ git -C "$upstream" config receive.advertisePushOptions true &&
+ test_must_fail git -C workbench push origin \
+ -o reviewers=user1,user2 \
+ HEAD:refs/for/main/topic \
+ >out-$test_count 2>&1 &&
+ filter_out_user_friendly_and_stable_output \
+ -e "/^To / { p; }" \
+ -e "/^ ! / { p; }" \
+ <out-$test_count >actual &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)
+ EOF
+ test_cmp expect actual &&
+ grep "remote: fatal: die with the --die-read-push-options option" out-$test_count &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+test_expect_success "setup proc-receive hook (hook --die-write-report, $PROTOCOL)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v --die-write-report
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/topic(A)
+test_expect_success "proc-receive: bad protocol (hook --die-write-report, $PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/main/topic \
+ >out-$test_count 2>&1 &&
+ filter_out_user_friendly_and_stable_output \
+ -e "/^To / { p; }" \
+ -e "/^ ! / { p; }" \
+ <out-$test_count >actual &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook)
+ EOF
+ test_cmp expect actual &&
+ grep "remote: fatal: die with the --die-write-report option" out-$test_count &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+test_expect_success "setup proc-receive hook (no report, $PROTOCOL)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : next(A) refs/for/main/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/main/topic >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next Z
+ > To <URL/of/upstream.git>
+ > * [new branch] HEAD -> next
+ > ! [remote rejected] HEAD -> refs/for/main/topic (proc-receive failed to report status)
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ <COMMIT-A> refs/heads/next
+ EOF
+'
+
+# Refs of upstream : main(A) next(A)
+# Refs of workbench: main(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)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/topic
+test_expect_success "proc-receive: bad protocol (no ref, $PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/main/topic\
+ >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ok Z
+ > remote: error: proc-receive reported incomplete status line: "ok" Z
+ > To <URL/of/upstream.git>
+ > ! [remote rejected] HEAD -> refs/for/main/topic (proc-receive failed to report status)
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+test_expect_success "setup proc-receive hook (unknown status, $PROTOCOL)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "xx refs/for/main/topic"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/topic
+test_expect_success "proc-receive: bad protocol (unknown status, $PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/main/topic \
+ >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> xx refs/for/main/topic Z
+ > remote: error: proc-receive reported bad status "xx" on ref "refs/for/main/topic" Z
+ > To <URL/of/upstream.git>
+ > ! [remote rejected] HEAD -> refs/for/main/topic (proc-receive failed to report status)
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
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..298a3d1
--- /dev/null
+++ b/t/t5411/test-0014-bad-protocol--porcelain.sh
@@ -0,0 +1,304 @@
+test_expect_success "setup proc-receive hook (unknown version, $PROTOCOL/porcelain)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v --version 2
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/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/main/topic \
+ >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >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/main/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 &&
+ format_and_save_expect <<-EOF &&
+ > remote: error: proc-receive version "2" is not supported Z
+ EOF
+ test_cmp expect actual-error &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+test_expect_success "setup proc-receive hook (hook --die-read-version, $PROTOCOL/porcelain)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v --die-read-version
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/topic(A)
+test_expect_success "proc-receive: bad protocol (hook --die-read-version, $PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain origin \
+ HEAD:refs/for/main/topic \
+ >out-$test_count 2>&1 &&
+ filter_out_user_friendly_and_stable_output \
+ -e "/^To / { p; n; p; n; p; }" \
+ <out-$test_count >actual &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ ! HEAD:refs/for/main/topic [remote rejected] (fail to run proc-receive hook)
+ Done
+ EOF
+ test_cmp expect actual &&
+ grep "remote: fatal: die with the --die-read-version option" out-$test_count &&
+ grep "remote: error: fail to negotiate version with proc-receive hook" out-$test_count &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+test_expect_success "setup proc-receive hook (hook --die-write-version, $PROTOCOL/porcelain)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v --die-write-version
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/topic(A)
+test_expect_success "proc-receive: bad protocol (hook --die-write-version, $PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain origin \
+ HEAD:refs/for/main/topic \
+ >out-$test_count 2>&1 &&
+ filter_out_user_friendly_and_stable_output \
+ -e "/^To / { p; n; p; n; p; }" \
+ <out-$test_count >actual &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ ! HEAD:refs/for/main/topic [remote rejected] (fail to run proc-receive hook)
+ Done
+ EOF
+ test_cmp expect actual &&
+ grep "remote: fatal: die with the --die-write-version option" out-$test_count &&
+ grep "remote: error: fail to negotiate version with proc-receive hook" out-$test_count &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+test_expect_success "setup proc-receive hook (hook --die-read-commands, $PROTOCOL/porcelain)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v --die-read-commands
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/topic(A)
+test_expect_success "proc-receive: bad protocol (hook --die-read-commands, $PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain origin \
+ HEAD:refs/for/main/topic \
+ >out-$test_count 2>&1 &&
+ filter_out_user_friendly_and_stable_output \
+ -e "/^To / { p; n; p; n; p; }" \
+ <out-$test_count >actual &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ ! HEAD:refs/for/main/topic [remote rejected] (fail to run proc-receive hook)
+ Done
+ EOF
+ test_cmp expect actual &&
+ grep "remote: fatal: die with the --die-read-commands option" out-$test_count &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+test_expect_success "setup proc-receive hook (hook --die-read-push-options, $PROTOCOL/porcelain)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v --die-read-push-options
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/topic(A)
+test_expect_success "proc-receive: bad protocol (hook --die-read-push-options, $PROTOCOL/porcelain)" '
+ git -C "$upstream" config receive.advertisePushOptions true &&
+ test_must_fail git -C workbench push --porcelain origin \
+ -o reviewers=user1,user2 \
+ HEAD:refs/for/main/topic \
+ >out-$test_count 2>&1 &&
+ filter_out_user_friendly_and_stable_output \
+ -e "/^To / { p; n; p; n; p; }" \
+ <out-$test_count >actual &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ ! HEAD:refs/for/main/topic [remote rejected] (fail to run proc-receive hook)
+ Done
+ EOF
+ test_cmp expect actual &&
+ grep "remote: fatal: die with the --die-read-push-options option" out-$test_count &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+test_expect_success "setup proc-receive hook (hook --die-write-report, $PROTOCOL/porcelain)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v --die-write-report
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/topic(A)
+test_expect_success "proc-receive: bad protocol (hook --die-write-report, $PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain origin \
+ HEAD:refs/for/main/topic \
+ >out-$test_count 2>&1 &&
+ filter_out_user_friendly_and_stable_output \
+ -e "/^To / { p; n; p; n; p; }" \
+ <out-$test_count >actual &&
+ cat >expect <<-EOF &&
+ To <URL/of/upstream.git>
+ ! HEAD:refs/for/main/topic [remote rejected] (fail to run proc-receive hook)
+ Done
+ EOF
+ test_cmp expect actual &&
+ grep "remote: fatal: die with the --die-write-report option" out-$test_count &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+test_expect_success "setup proc-receive hook (no report, $PROTOCOL/porcelain)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : next(A) refs/for/main/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/main/topic >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next Z
+ > To <URL/of/upstream.git>
+ > * HEAD:refs/heads/next [new branch]
+ > ! HEAD:refs/for/main/topic [remote rejected] (proc-receive failed to report status)
+ > Done
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ <COMMIT-A> refs/heads/next
+ EOF
+'
+
+# Refs of upstream : main(A) next(A)
+# Refs of workbench: main(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)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/topic
+test_expect_success "proc-receive: bad protocol (no ref, $PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain origin \
+ HEAD:refs/for/main/topic\
+ >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ok Z
+ > remote: error: proc-receive reported incomplete status line: "ok" Z
+ > To <URL/of/upstream.git>
+ > ! HEAD:refs/for/main/topic [remote rejected] (proc-receive failed to report status)
+ > Done
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+test_expect_success "setup proc-receive hook (unknown status, $PROTOCOL/porcelain)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "xx refs/for/main/topic"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/topic
+test_expect_success "proc-receive: bad protocol (unknown status, $PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain origin \
+ HEAD:refs/for/main/topic \
+ >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> xx refs/for/main/topic Z
+ > remote: error: proc-receive reported bad status "xx" on ref "refs/for/main/topic" Z
+ > To <URL/of/upstream.git>
+ > ! HEAD:refs/for/main/topic [remote rejected] (proc-receive failed to report status)
+ > Done
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
diff --git a/t/t5411/test-0020-report-ng.sh b/t/t5411/test-0020-report-ng.sh
new file mode 100644
index 0000000..6347c96
--- /dev/null
+++ b/t/t5411/test-0020-report-ng.sh
@@ -0,0 +1,63 @@
+test_expect_success "setup proc-receive hook (ng, no message, $PROTOCOL)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ng refs/for/main/topic"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/topic
+test_expect_success "proc-receive: fail to update (ng, no message, $PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/main/topic \
+ >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ng refs/for/main/topic Z
+ > To <URL/of/upstream.git>
+ > ! [remote rejected] HEAD -> refs/for/main/topic (failed)
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+test_expect_success "setup proc-receive hook (ng message, $PROTOCOL)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ng refs/for/main/topic error msg"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/topic
+test_expect_success "proc-receive: fail to update (ng, with message, $PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/main/topic \
+ >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ng refs/for/main/topic error msg Z
+ > To <URL/of/upstream.git>
+ > ! [remote rejected] HEAD -> refs/for/main/topic (error msg)
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
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..502b34f
--- /dev/null
+++ b/t/t5411/test-0021-report-ng--porcelain.sh
@@ -0,0 +1,65 @@
+test_expect_success "setup proc-receive hook (ng, no message, $PROTOCOL/porcelain)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ng refs/for/main/topic"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/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/main/topic \
+ >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ng refs/for/main/topic Z
+ > To <URL/of/upstream.git>
+ > ! HEAD:refs/for/main/topic [remote rejected] (failed)
+ > Done
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+test_expect_success "setup proc-receive hook (ng message, $PROTOCOL/porcelain)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ng refs/for/main/topic error msg"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/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/main/topic \
+ >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ng refs/for/main/topic error msg Z
+ > To <URL/of/upstream.git>
+ > ! HEAD:refs/for/main/topic [remote rejected] (error msg)
+ > Done
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
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..7744392
--- /dev/null
+++ b/t/t5411/test-0022-report-unexpect-ref.sh
@@ -0,0 +1,43 @@
+test_expect_success "setup proc-receive hook (unexpected ref, $PROTOCOL)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/heads/main"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : (B) refs/for/main/topic
+test_expect_success "proc-receive: report unexpected ref ($PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ $B:refs/heads/main \
+ HEAD:refs/for/main/topic \
+ >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ok refs/heads/main Z
+ > remote: error: proc-receive reported status on unexpected ref: refs/heads/main Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/main Z
+ > To <URL/of/upstream.git>
+ > <COMMIT-A>..<COMMIT-B> <COMMIT-B> -> main
+ > ! [remote rejected] HEAD -> refs/for/main/topic (proc-receive failed to report status)
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-B> refs/heads/main
+ EOF
+'
+
+# Refs of upstream : main(B)
+# Refs of workbench: main(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL)" '
+ git -C "$upstream" update-ref refs/heads/main $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..6d116ef
--- /dev/null
+++ b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh
@@ -0,0 +1,44 @@
+test_expect_success "setup proc-receive hook (unexpected ref, $PROTOCOL/porcelain)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/heads/main"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : (B) refs/for/main/topic
+test_expect_success "proc-receive: report unexpected ref ($PROTOCOL/porcelain)" '
+ test_must_fail git -C workbench push --porcelain origin \
+ $B:refs/heads/main \
+ HEAD:refs/for/main/topic \
+ >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ok refs/heads/main Z
+ > remote: error: proc-receive reported status on unexpected ref: refs/heads/main Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/main Z
+ > To <URL/of/upstream.git>
+ > <COMMIT-B>:refs/heads/main <COMMIT-A>..<COMMIT-B>
+ > ! HEAD:refs/for/main/topic [remote rejected] (proc-receive failed to report status)
+ > Done
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-B> refs/heads/main
+ EOF
+'
+
+# Refs of upstream : main(B)
+# Refs of workbench: main(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+ git -C "$upstream" update-ref refs/heads/main $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..619ca2f
--- /dev/null
+++ b/t/t5411/test-0024-report-unknown-ref.sh
@@ -0,0 +1,32 @@
+test_expect_success "setup proc-receive hook (unexpected ref, $PROTOCOL)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/main/topic"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(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-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: error: proc-receive reported status on unknown ref: refs/for/main/topic Z
+ > 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 &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
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..8b3f5d0
--- /dev/null
+++ b/t/t5411/test-0025-report-unknown-ref--porcelain.sh
@@ -0,0 +1,33 @@
+test_expect_success "setup proc-receive hook (unexpected ref, $PROTOCOL/porcelain)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/main/topic"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(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-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: error: proc-receive reported status on unknown ref: refs/for/main/topic Z
+ > 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 &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
diff --git a/t/t5411/test-0026-push-options.sh b/t/t5411/test-0026-push-options.sh
new file mode 100644
index 0000000..510fff3
--- /dev/null
+++ b/t/t5411/test-0026-push-options.sh
@@ -0,0 +1,133 @@
+test_expect_success "setup proc-receive hook and disable push-options ($PROTOCOL)" '
+ git -C "$upstream" config receive.advertisePushOptions false &&
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/main/topic"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push -o ... : refs/for/main/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/main/topic \
+ >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ test_grep "fatal: the receiving end does not support push options" \
+ actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+test_expect_success "enable push options ($PROTOCOL)" '
+ git -C "$upstream" config receive.advertisePushOptions true
+'
+
+test_expect_success "setup version=0 for proc-receive hook ($PROTOCOL)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ --version 0 \
+ -r "ok refs/for/main/topic"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push -o ... : next(A) refs/for/main/topic
+test_expect_success "proc-receive: ignore push-options for version 0 ($PROTOCOL)" '
+ git -C workbench push \
+ --atomic \
+ -o issue=123 \
+ -o reviewer=user1 \
+ origin \
+ HEAD:refs/heads/next \
+ HEAD:refs/for/main/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > To <URL/of/upstream.git>
+ > * [new branch] HEAD -> next
+ > * [new reference] HEAD -> refs/for/main/topic
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ <COMMIT-A> refs/heads/next
+ EOF
+'
+
+test_expect_success "restore proc-receive hook ($PROTOCOL)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/main/topic"
+ EOF
+'
+
+# Refs of upstream : main(A) next(A)
+# Refs of workbench: main(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL)" '
+ git -C "$upstream" update-ref -d refs/heads/next
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push -o ... : next(A) refs/for/main/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/main/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive: atomic push_options Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive< issue=123 Z
+ > remote: proc-receive< reviewer=user1 Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > To <URL/of/upstream.git>
+ > * [new branch] HEAD -> next
+ > * [new reference] HEAD -> refs/for/main/topic
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ <COMMIT-A> refs/heads/next
+ EOF
+'
+
+# Refs of upstream : main(A) next(A)
+# Refs of workbench: main(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..9435457
--- /dev/null
+++ b/t/t5411/test-0027-push-options--porcelain.sh
@@ -0,0 +1,138 @@
+test_expect_success "setup proc-receive hook and disable push-options ($PROTOCOL/porcelain)" '
+ git -C "$upstream" config receive.advertisePushOptions false &&
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/main/topic"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push -o ... : refs/for/main/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/main/topic \
+ >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ test_grep "fatal: the receiving end does not support push options" \
+ actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+test_expect_success "enable push options ($PROTOCOL/porcelain)" '
+ git -C "$upstream" config receive.advertisePushOptions true
+'
+
+test_expect_success "setup version=0 for proc-receive hook ($PROTOCOL/porcelain)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ --version 0 \
+ -r "ok refs/for/main/topic"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push -o ... : next(A) refs/for/main/topic
+test_expect_success "proc-receive: ignore push-options for version 0 ($PROTOCOL/porcelain)" '
+ git -C workbench push \
+ --porcelain \
+ --atomic \
+ -o issue=123 \
+ -o reviewer=user1 \
+ origin \
+ HEAD:refs/heads/next \
+ HEAD:refs/for/main/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > To <URL/of/upstream.git>
+ > * HEAD:refs/heads/next [new branch]
+ > * HEAD:refs/for/main/topic [new reference]
+ > Done
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ <COMMIT-A> refs/heads/next
+ EOF
+'
+
+test_expect_success "restore proc-receive hook ($PROTOCOL/porcelain)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/main/topic"
+ EOF
+'
+
+# Refs of upstream : main(A) next(A)
+# Refs of workbench: main(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+ git -C "$upstream" update-ref -d refs/heads/next
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push -o ... : next(A) refs/for/main/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/main/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive: atomic push_options Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive< issue=123 Z
+ > remote: proc-receive< reviewer=user1 Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > To <URL/of/upstream.git>
+ > * HEAD:refs/heads/next [new branch]
+ > * HEAD:refs/for/main/topic [new reference]
+ > Done
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ <COMMIT-A> refs/heads/next
+ EOF
+'
+
+# Refs of upstream : main(A) next(A)
+# Refs of workbench: main(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..0f190a6
--- /dev/null
+++ b/t/t5411/test-0030-report-ok.sh
@@ -0,0 +1,33 @@
+test_expect_success "setup proc-receive hook (ok, $PROTOCOL)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/main/topic"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/topic
+test_expect_success "proc-receive: ok ($PROTOCOL)" '
+ git -C workbench push origin \
+ HEAD:refs/for/main/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > To <URL/of/upstream.git>
+ > * [new reference] HEAD -> refs/for/main/topic
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
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..7ec3981
--- /dev/null
+++ b/t/t5411/test-0031-report-ok--porcelain.sh
@@ -0,0 +1,34 @@
+test_expect_success "setup proc-receive hook (ok, $PROTOCOL/porcelain)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/main/topic"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/topic
+test_expect_success "proc-receive: ok ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain origin \
+ HEAD:refs/for/main/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > To <URL/of/upstream.git>
+ > * HEAD:refs/for/main/topic [new reference]
+ > Done
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
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..07733b9
--- /dev/null
+++ b/t/t5411/test-0032-report-with-options.sh
@@ -0,0 +1,253 @@
+test_expect_success "setup proc-receive hook (option without matching ok, $PROTOCOL)" '
+ test_hook -C "$upstream" --clobber 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 : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/main/topic(A)
+test_expect_success "proc-receive: report option without matching ok ($PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ HEAD:refs/for/main/topic \
+ >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> option refname refs/pull/123/head Z
+ > remote: proc-receive> option old-oid <COMMIT-B> Z
+ > remote: error: proc-receive reported "option" without a matching "ok/ng" directive Z
+ > To <URL/of/upstream.git>
+ > ! [remote rejected] HEAD -> refs/for/main/topic (proc-receive failed to report status)
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option refname, $PROTOCOL)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/main/topic" \
+ -r "option refname refs/pull/123/head"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/main/topic(A)
+test_expect_success "proc-receive: report option refname ($PROTOCOL)" '
+ git -C workbench push origin \
+ HEAD:refs/for/main/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option refname refs/pull/123/head Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head Z
+ > 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)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/main/topic" \
+ -r "option refname refs/pull/123/head" \
+ -r "option forced-update"
+ EOF
+'
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/main/topic(A)
+test_expect_success "proc-receive: report option refname and forced-update ($PROTOCOL)" '
+ git -C workbench push origin \
+ HEAD:refs/for/main/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option refname refs/pull/123/head Z
+ > remote: proc-receive> option forced-update Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head Z
+ > 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)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/main/topic" \
+ -r "option refname refs/pull/123/head" \
+ -r "option old-oid $B"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/main/topic(A)
+test_expect_success "proc-receive: report option refname and old-oid ($PROTOCOL)" '
+ git -C workbench push origin \
+ HEAD:refs/for/main/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option refname refs/pull/123/head Z
+ > remote: proc-receive> option old-oid <COMMIT-B> Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head Z
+ > To <URL/of/upstream.git>
+ > <COMMIT-B>..<COMMIT-A> HEAD -> refs/pull/123/head
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option old-oid, $PROTOCOL)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/main/topic" \
+ -r "option old-oid $B"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/main/topic(A)
+test_expect_success "proc-receive: report option old-oid ($PROTOCOL)" '
+ git -C workbench push origin \
+ HEAD:refs/for/main/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option old-oid <COMMIT-B> Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/main/topic Z
+ > To <URL/of/upstream.git>
+ > <COMMIT-B>..<COMMIT-A> HEAD -> refs/for/main/topic
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option old-oid and new-oid, $PROTOCOL)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/main/topic" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/main/topic(A)
+test_expect_success "proc-receive: report option old-oid and new-oid ($PROTOCOL)" '
+ git -C workbench push origin \
+ HEAD:refs/for/main/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option old-oid <COMMIT-A> Z
+ > remote: proc-receive> option new-oid <COMMIT-B> Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/main/topic Z
+ > To <URL/of/upstream.git>
+ > <COMMIT-A>..<COMMIT-B> HEAD -> refs/for/main/topic
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (report with multiple rewrites, $PROTOCOL)" '
+ test_hook -C "$upstream" --clobber 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/main/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 : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/main/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/main/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ok refs/for/a/b/c/topic Z
+ > remote: proc-receive> ok refs/for/next/topic Z
+ > remote: proc-receive> option refname refs/pull/123/head Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option refname refs/pull/124/head Z
+ > remote: proc-receive> option old-oid <COMMIT-B> Z
+ > remote: proc-receive> option forced-update Z
+ > remote: proc-receive> option new-oid <COMMIT-A> Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic Z
+ > remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head Z
+ > To <URL/of/upstream.git>
+ > * [new reference] HEAD -> refs/pull/123/head
+ > * [new reference] HEAD -> refs/for/a/b/c/topic
+ > + <COMMIT-B>...<COMMIT-A> HEAD -> refs/pull/124/head (forced update)
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
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..2e1831b
--- /dev/null
+++ b/t/t5411/test-0033-report-with-options--porcelain.sh
@@ -0,0 +1,262 @@
+test_expect_success "setup proc-receive hook (option without matching ok, $PROTOCOL/porcelain)" '
+ test_hook -C "$upstream" --clobber 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 : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/main/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/main/topic \
+ >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> option refname refs/pull/123/head Z
+ > remote: proc-receive> option old-oid <COMMIT-B> Z
+ > remote: error: proc-receive reported "option" without a matching "ok/ng" directive Z
+ > To <URL/of/upstream.git>
+ > ! HEAD:refs/for/main/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)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/main/topic" \
+ -r "option refname refs/pull/123/head"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/main/topic(A)
+test_expect_success "proc-receive: report option refname ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain origin \
+ HEAD:refs/for/main/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option refname refs/pull/123/head Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head Z
+ > 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)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/main/topic" \
+ -r "option refname refs/pull/123/head" \
+ -r "option forced-update"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/main/topic(A)
+test_expect_success "proc-receive: report option refname and forced-update ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain origin \
+ HEAD:refs/for/main/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option refname refs/pull/123/head Z
+ > remote: proc-receive> option forced-update Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head Z
+ > 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)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/main/topic" \
+ -r "option refname refs/pull/123/head" \
+ -r "option old-oid $B"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/main/topic(A)
+test_expect_success "proc-receive: report option refname and old-oid ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain origin \
+ HEAD:refs/for/main/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option refname refs/pull/123/head Z
+ > remote: proc-receive> option old-oid <COMMIT-B> Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head Z
+ > To <URL/of/upstream.git>
+ > HEAD:refs/pull/123/head <COMMIT-B>..<COMMIT-A>
+ > Done
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option old-oid, $PROTOCOL/porcelain)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/main/topic" \
+ -r "option old-oid $B"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/main/topic(A)
+test_expect_success "proc-receive: report option old-oid ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain origin \
+ HEAD:refs/for/main/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option old-oid <COMMIT-B> Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/main/topic Z
+ > To <URL/of/upstream.git>
+ > HEAD:refs/for/main/topic <COMMIT-B>..<COMMIT-A>
+ > Done
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (option old-oid and new-oid, $PROTOCOL/porcelain)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/main/topic" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/main/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/main/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option old-oid <COMMIT-A> Z
+ > remote: proc-receive> option new-oid <COMMIT-B> Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/main/topic Z
+ > To <URL/of/upstream.git>
+ > HEAD:refs/for/main/topic <COMMIT-A>..<COMMIT-B>
+ > Done
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success "setup proc-receive hook (report with multiple rewrites, $PROTOCOL/porcelain)" '
+ test_hook -C "$upstream" --clobber 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/main/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 : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/main/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/main/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ok refs/for/a/b/c/topic Z
+ > remote: proc-receive> ok refs/for/next/topic Z
+ > remote: proc-receive> option refname refs/pull/123/head Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option refname refs/pull/124/head Z
+ > remote: proc-receive> option old-oid <COMMIT-B> Z
+ > remote: proc-receive> option forced-update Z
+ > remote: proc-receive> option new-oid <COMMIT-A> Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic Z
+ > remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head Z
+ > 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 <COMMIT-B>...<COMMIT-A> (forced update)
+ > Done
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
diff --git a/t/t5411/test-0034-report-ft.sh b/t/t5411/test-0034-report-ft.sh
new file mode 100644
index 0000000..0e37535
--- /dev/null
+++ b/t/t5411/test-0034-report-ft.sh
@@ -0,0 +1,42 @@
+test_expect_success "setup proc-receive hook (ft, $PROTOCOL)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/main/topic" \
+ -r "option fall-through"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/topic(B)
+test_expect_success "proc-receive: fall throught, let receive-pack to execute ($PROTOCOL)" '
+ git -C workbench push origin \
+ $B:refs/for/main/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/main/topic Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option fall-through Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/main/topic Z
+ > To <URL/of/upstream.git>
+ > * [new reference] <COMMIT-B> -> refs/for/main/topic
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-B> refs/for/main/topic
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+# Refs of upstream : main(A) refs/for/main/topic(A)
+# Refs of workbench: main(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL)" '
+ git -C "$upstream" update-ref -d refs/for/main/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..b9a0518
--- /dev/null
+++ b/t/t5411/test-0035-report-ft--porcelain.sh
@@ -0,0 +1,43 @@
+test_expect_success "setup proc-receive hook (fall-through, $PROTOCOL/porcelain)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-\EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/main/topic" \
+ -r "option fall-through"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/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/main/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/main/topic Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option fall-through Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/main/topic Z
+ > To <URL/of/upstream.git>
+ > * <COMMIT-B>:refs/for/main/topic [new reference]
+ > Done
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-B> refs/for/main/topic
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+# Refs of upstream : main(A) refs/for/main/topic(A)
+# Refs of workbench: main(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+ git -C "$upstream" update-ref -d refs/for/main/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..889e970
--- /dev/null
+++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh
@@ -0,0 +1,221 @@
+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)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/main/topic" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B" \
+ -r "ok refs/for/main/topic" \
+ -r "option refname refs/changes/24/124/1" \
+ -r "option old-oid $ZERO_OID" \
+ -r "option new-oid $A" \
+ -r "ok refs/for/main/topic" \
+ -r "option refname refs/changes/25/125/1" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/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/main/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option old-oid <COMMIT-A> Z
+ > remote: proc-receive> option new-oid <COMMIT-B> Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option refname refs/changes/24/124/1 Z
+ > remote: proc-receive> option old-oid <ZERO-OID> Z
+ > remote: proc-receive> option new-oid <COMMIT-A> Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option refname refs/changes/25/125/1 Z
+ > remote: proc-receive> option old-oid <COMMIT-A> Z
+ > remote: proc-receive> option new-oid <COMMIT-B> Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/main/topic Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1 Z
+ > remote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1 Z
+ > To <URL/of/upstream.git>
+ > <COMMIT-A>..<COMMIT-B> HEAD -> refs/for/main/topic
+ > * [new reference] HEAD -> refs/changes/24/124/1
+ > <COMMIT-A>..<COMMIT-B> HEAD -> refs/changes/25/125/1
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+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/main/topic
+ EOF
+ test_cmp expect actual &&
+ git -C workbench update-ref -d refs/t/for/main/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)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/main/topic" \
+ -r "option refname refs/changes/24/124/1" \
+ -r "option old-oid $ZERO_OID" \
+ -r "option new-oid $A" \
+ -r "ok refs/for/main/topic" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B" \
+ -r "ok refs/for/main/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 : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/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/main/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option refname refs/changes/24/124/1 Z
+ > remote: proc-receive> option old-oid <ZERO-OID> Z
+ > remote: proc-receive> option new-oid <COMMIT-A> Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option old-oid <COMMIT-A> Z
+ > remote: proc-receive> option new-oid <COMMIT-B> Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option refname refs/changes/25/125/1 Z
+ > remote: proc-receive> option old-oid <COMMIT-B> Z
+ > remote: proc-receive> option new-oid <COMMIT-A> Z
+ > remote: proc-receive> option forced-update Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1 Z
+ > remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/main/topic Z
+ > remote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1 Z
+ > To <URL/of/upstream.git>
+ > * [new reference] HEAD -> refs/changes/24/124/1
+ > <COMMIT-A>..<COMMIT-B> HEAD -> refs/for/main/topic
+ > + <COMMIT-B>...<COMMIT-A> HEAD -> refs/changes/25/125/1 (forced update)
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+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/main/topic
+ EOF
+ test_cmp expect actual &&
+ git -C workbench update-ref -d refs/t/for/main/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)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/main/topic" \
+ -r "option refname refs/changes/23/123/1" \
+ -r "ok refs/for/main/topic" \
+ -r "option refname refs/changes/24/124/2" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/topic(A)
+test_expect_success "proc-receive: multiple rewrites for one ref ($PROTOCOL)" '
+ git -C workbench push origin \
+ HEAD:refs/for/main/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option refname refs/changes/23/123/1 Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option refname refs/changes/24/124/2 Z
+ > remote: proc-receive> option old-oid <COMMIT-A> Z
+ > remote: proc-receive> option new-oid <COMMIT-B> Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1 Z
+ > remote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2 Z
+ > To <URL/of/upstream.git>
+ > * [new reference] HEAD -> refs/changes/23/123/1
+ > <COMMIT-A>..<COMMIT-B> HEAD -> refs/changes/24/124/2
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+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..1e523b1
--- /dev/null
+++ b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh
@@ -0,0 +1,166 @@
+test_expect_success "setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL/porcelain)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/main/topic" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B" \
+ -r "ok refs/for/main/topic" \
+ -r "option refname refs/changes/24/124/1" \
+ -r "option old-oid $ZERO_OID" \
+ -r "option new-oid $A" \
+ -r "ok refs/for/main/topic" \
+ -r "option refname refs/changes/25/125/1" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/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/main/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option old-oid <COMMIT-A> Z
+ > remote: proc-receive> option new-oid <COMMIT-B> Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option refname refs/changes/24/124/1 Z
+ > remote: proc-receive> option old-oid <ZERO-OID> Z
+ > remote: proc-receive> option new-oid <COMMIT-A> Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option refname refs/changes/25/125/1 Z
+ > remote: proc-receive> option old-oid <COMMIT-A> Z
+ > remote: proc-receive> option new-oid <COMMIT-B> Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/main/topic Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1 Z
+ > remote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1 Z
+ > To <URL/of/upstream.git>
+ > HEAD:refs/for/main/topic <COMMIT-A>..<COMMIT-B>
+ > * HEAD:refs/changes/24/124/1 [new reference]
+ > HEAD:refs/changes/25/125/1 <COMMIT-A>..<COMMIT-B>
+ > Done
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+test_expect_success "setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL/porcelain)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/main/topic" \
+ -r "option refname refs/changes/24/124/1" \
+ -r "option old-oid $ZERO_OID" \
+ -r "option new-oid $A" \
+ -r "ok refs/for/main/topic" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B" \
+ -r "ok refs/for/main/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 : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/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/main/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option refname refs/changes/24/124/1 Z
+ > remote: proc-receive> option old-oid <ZERO-OID> Z
+ > remote: proc-receive> option new-oid <COMMIT-A> Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option old-oid <COMMIT-A> Z
+ > remote: proc-receive> option new-oid <COMMIT-B> Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option refname refs/changes/25/125/1 Z
+ > remote: proc-receive> option old-oid <COMMIT-B> Z
+ > remote: proc-receive> option new-oid <COMMIT-A> Z
+ > remote: proc-receive> option forced-update Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1 Z
+ > remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/main/topic Z
+ > remote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1 Z
+ > To <URL/of/upstream.git>
+ > * HEAD:refs/changes/24/124/1 [new reference]
+ > HEAD:refs/for/main/topic <COMMIT-A>..<COMMIT-B>
+ > + HEAD:refs/changes/25/125/1 <COMMIT-B>...<COMMIT-A> (forced update)
+ > Done
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+test_expect_success "setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL/porcelain)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/for/main/topic" \
+ -r "option refname refs/changes/23/123/1" \
+ -r "ok refs/for/main/topic" \
+ -r "option refname refs/changes/24/124/2" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : refs/for/main/topic(A)
+test_expect_success "proc-receive: multiple rewrites for one ref ($PROTOCOL/porcelain)" '
+ git -C workbench push --porcelain origin \
+ HEAD:refs/for/main/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option refname refs/changes/23/123/1 Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option refname refs/changes/24/124/2 Z
+ > remote: proc-receive> option old-oid <COMMIT-A> Z
+ > remote: proc-receive> option new-oid <COMMIT-B> Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1 Z
+ > remote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2 Z
+ > To <URL/of/upstream.git>
+ > * HEAD:refs/changes/23/123/1 [new reference]
+ > HEAD:refs/changes/24/124/2 <COMMIT-A>..<COMMIT-B>
+ > Done
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
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..4c70e84
--- /dev/null
+++ b/t/t5411/test-0038-report-mixed-refs.sh
@@ -0,0 +1,87 @@
+test_expect_success "setup proc-receive hook ($PROTOCOL)" '
+ test_hook -C "$upstream" --clobber 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/main/topic" \
+ -r "option refname refs/for/main/topic" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : (B) bar(A) baz(A) refs/for/next/topic(A) foo(A) refs/for/main/topic(A)
+test_expect_success "proc-receive: report update of mixed refs ($PROTOCOL)" '
+ test_must_fail git -C workbench push origin \
+ $B:refs/heads/main \
+ 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/main/topic \
+ HEAD:refs/for/next/topic3 \
+ >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2 Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1 Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3 Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2 Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1 Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3 Z
+ > remote: proc-receive> ok refs/for/next/topic2 Z
+ > remote: proc-receive> ng refs/for/next/topic1 fail to call Web API Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option refname refs/for/main/topic Z
+ > remote: proc-receive> option old-oid <COMMIT-A> Z
+ > remote: proc-receive> option new-oid <COMMIT-B> Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/main Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2 Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo Z
+ > remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/main/topic Z
+ > To <URL/of/upstream.git>
+ > <COMMIT-A>..<COMMIT-B> <COMMIT-B> -> main
+ > * [new branch] HEAD -> bar
+ > * [new branch] HEAD -> baz
+ > * [new reference] HEAD -> refs/for/next/topic2
+ > * [new branch] HEAD -> foo
+ > <COMMIT-A>..<COMMIT-B> HEAD -> refs/for/main/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 &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/bar
+ <COMMIT-A> refs/heads/baz
+ <COMMIT-A> refs/heads/foo
+ <COMMIT-B> refs/heads/main
+ EOF
+'
+
+# Refs of upstream : main(B) foo(A) bar(A)) baz(A)
+# Refs of workbench: main(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL)" '
+ (
+ cd "$upstream" &&
+ git update-ref refs/heads/main $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..40f4c5b
--- /dev/null
+++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh
@@ -0,0 +1,89 @@
+test_expect_success "setup proc-receive hook ($PROTOCOL/porcelain)" '
+ test_hook -C "$upstream" --clobber 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/main/topic" \
+ -r "option refname refs/for/main/topic" \
+ -r "option old-oid $A" \
+ -r "option new-oid $B"
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : (B) bar(A) baz(A) refs/for/next/topic(A) foo(A) refs/for/main/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/main \
+ 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/main/topic \
+ HEAD:refs/for/next/topic3 \
+ >out-$test_count 2>&1 &&
+ make_user_friendly_and_stable_output <out-$test_count >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2 Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1 Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3 Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2 Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1 Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3 Z
+ > remote: proc-receive> ok refs/for/next/topic2 Z
+ > remote: proc-receive> ng refs/for/next/topic1 fail to call Web API Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option refname refs/for/main/topic Z
+ > remote: proc-receive> option old-oid <COMMIT-A> Z
+ > remote: proc-receive> option new-oid <COMMIT-B> Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/main Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2 Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo Z
+ > remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/main/topic Z
+ > To <URL/of/upstream.git>
+ > <COMMIT-B>:refs/heads/main <COMMIT-A>..<COMMIT-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/main/topic <COMMIT-A>..<COMMIT-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 &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/bar
+ <COMMIT-A> refs/heads/baz
+ <COMMIT-A> refs/heads/foo
+ <COMMIT-B> refs/heads/main
+ EOF
+'
+
+# Refs of upstream : main(B) foo(A) bar(A)) baz(A)
+# Refs of workbench: main(A) tags/v123
+test_expect_success "cleanup ($PROTOCOL/porcelain)" '
+ (
+ cd "$upstream" &&
+ git update-ref refs/heads/main $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..7ae3851
--- /dev/null
+++ b/t/t5411/test-0040-process-all-refs.sh
@@ -0,0 +1,111 @@
+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 : main(A)
+# Refs of workbench: main(A) tags/v123
+test_expect_success "setup upstream branches ($PROTOCOL)" '
+ (
+ cd "$upstream" &&
+ git update-ref refs/heads/main $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)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/heads/main" \
+ -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/main/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 : main(B) foo(A) bar(A)) baz(A)
+# Refs of workbench: main(A) tags/v123
+# git push -f : main(A) (NULL) (B) refs/for/main/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/main \
+ :refs/heads/foo \
+ $B:refs/heads/bar \
+ HEAD:refs/for/main/topic \
+ HEAD:refs/for/next/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar Z
+ > remote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo Z
+ > remote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/main Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar Z
+ > remote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo Z
+ > remote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/main Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic Z
+ > remote: proc-receive> ok refs/heads/main Z
+ > remote: proc-receive> option fall-through Z
+ > remote: proc-receive> ok refs/heads/foo Z
+ > remote: proc-receive> option fall-through Z
+ > remote: proc-receive> ok refs/heads/bar Z
+ > remote: proc-receive> option fall-through Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option refname refs/pull/123/head Z
+ > remote: proc-receive> option old-oid <COMMIT-A> Z
+ > remote: proc-receive> option new-oid <COMMIT-B> Z
+ > remote: proc-receive> ok refs/for/next/topic Z
+ > remote: proc-receive> option refname refs/pull/124/head Z
+ > remote: proc-receive> option old-oid <COMMIT-B> Z
+ > remote: proc-receive> option new-oid <COMMIT-A> Z
+ > remote: proc-receive> option forced-update Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar Z
+ > remote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo Z
+ > remote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/main Z
+ > remote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head Z
+ > remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head Z
+ > To <URL/of/upstream.git>
+ > <COMMIT-A>..<COMMIT-B> <COMMIT-B> -> bar
+ > - [deleted] foo
+ > + <COMMIT-B>...<COMMIT-A> HEAD -> main (forced update)
+ > <COMMIT-A>..<COMMIT-B> HEAD -> refs/pull/123/head
+ > + <COMMIT-B>...<COMMIT-A> HEAD -> refs/pull/124/head (forced update)
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-B> refs/heads/bar
+ <COMMIT-A> refs/heads/baz
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+# Refs of upstream : main(A) bar(A) baz(B)
+# Refs of workbench: main(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..02e1e08
--- /dev/null
+++ b/t/t5411/test-0041-process-all-refs--porcelain.sh
@@ -0,0 +1,112 @@
+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 : main(A)
+# Refs of workbench: main(A) tags/v123
+test_expect_success "setup upstream branches ($PROTOCOL/porcelain)" '
+ (
+ cd "$upstream" &&
+ git update-ref refs/heads/main $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)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/heads/main" \
+ -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/main/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 : main(B) foo(A) bar(A)) baz(A)
+# Refs of workbench: main(A) tags/v123
+# git push -f : main(A) (NULL) (B) refs/for/main/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/main \
+ :refs/heads/foo \
+ $B:refs/heads/bar \
+ HEAD:refs/for/main/topic \
+ HEAD:refs/for/next/topic \
+ >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar Z
+ > remote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo Z
+ > remote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/main Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar Z
+ > remote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo Z
+ > remote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/main Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic Z
+ > remote: proc-receive> ok refs/heads/main Z
+ > remote: proc-receive> option fall-through Z
+ > remote: proc-receive> ok refs/heads/foo Z
+ > remote: proc-receive> option fall-through Z
+ > remote: proc-receive> ok refs/heads/bar Z
+ > remote: proc-receive> option fall-through Z
+ > remote: proc-receive> ok refs/for/main/topic Z
+ > remote: proc-receive> option refname refs/pull/123/head Z
+ > remote: proc-receive> option old-oid <COMMIT-A> Z
+ > remote: proc-receive> option new-oid <COMMIT-B> Z
+ > remote: proc-receive> ok refs/for/next/topic Z
+ > remote: proc-receive> option refname refs/pull/124/head Z
+ > remote: proc-receive> option old-oid <COMMIT-B> Z
+ > remote: proc-receive> option new-oid <COMMIT-A> Z
+ > remote: proc-receive> option forced-update Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar Z
+ > remote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo Z
+ > remote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/main Z
+ > remote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head Z
+ > remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head Z
+ > To <URL/of/upstream.git>
+ > <COMMIT-B>:refs/heads/bar <COMMIT-A>..<COMMIT-B>
+ > - :refs/heads/foo [deleted]
+ > + HEAD:refs/heads/main <COMMIT-B>...<COMMIT-A> (forced update)
+ > HEAD:refs/pull/123/head <COMMIT-A>..<COMMIT-B>
+ > + HEAD:refs/pull/124/head <COMMIT-B>...<COMMIT-A> (forced update)
+ > Done
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-B> refs/heads/bar
+ <COMMIT-A> refs/heads/baz
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+# Refs of upstream : main(A) bar(A) baz(B)
+# Refs of workbench: main(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..7efdfe5
--- /dev/null
+++ b/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh
@@ -0,0 +1,129 @@
+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/main &&
+ git config --add receive.procReceiveRefs ad:refs/heads &&
+ git config --add receive.procReceiveRefs "a!:refs/heads"
+ )
+'
+
+test_expect_success "setup proc-receive hook ($PROTOCOL)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/heads/main" \
+ -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 : main(A)
+# Refs of workbench: main(A) tags/v123
+# git push : main(B) tags/v123
+test_expect_success "proc-receive: update branch and new tag ($PROTOCOL)" '
+ git -C workbench push origin \
+ $B:refs/heads/main \
+ v123 >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main Z
+ > remote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123 Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/main Z
+ > remote: proc-receive< <ZERO-OID> <TAG-v123> refs/tags/v123 Z
+ > remote: proc-receive> ok refs/heads/main Z
+ > remote: proc-receive> option refname refs/pull/123/head Z
+ > remote: proc-receive> option old-oid <COMMIT-A> Z
+ > remote: proc-receive> option new-oid <COMMIT-B> Z
+ > remote: proc-receive> ok refs/tags/v123 Z
+ > remote: proc-receive> option refname refs/pull/124/head Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head Z
+ > remote: post-receive< <ZERO-OID> <TAG-v123> refs/pull/124/head Z
+ > To <URL/of/upstream.git>
+ > <COMMIT-A>..<COMMIT-B> <COMMIT-B> -> refs/pull/123/head
+ > * [new reference] v123 -> refs/pull/124/head
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ EOF
+'
+
+# Refs of upstream : main(A)
+# Refs of workbench: main(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 &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ <COMMIT-A> refs/heads/topic
+ <TAG-v123> refs/tags/v123
+ EOF
+'
+
+test_expect_success "setup proc-receive hook ($PROTOCOL)" '
+ test_hook -C "$upstream" --clobber proc-receive <<-EOF
+ printf >&2 "# proc-receive hook\n"
+ test-tool proc-receive -v \
+ -r "ok refs/heads/main" \
+ -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 : main(A) topic(A) tags/v123
+# Refs of workbench: main(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/main \
+ $B:refs/heads/topic \
+ $A:refs/heads/next \
+ :refs/tags/v123 >out 2>&1 &&
+ make_user_friendly_and_stable_output <out >actual &&
+ format_and_save_expect <<-EOF &&
+ > remote: # pre-receive hook Z
+ > remote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/main Z
+ > remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/topic Z
+ > remote: pre-receive< <TAG-v123> <ZERO-OID> refs/tags/v123 Z
+ > remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next Z
+ > remote: # proc-receive hook Z
+ > remote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/main Z
+ > remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/heads/next Z
+ > remote: proc-receive> ok refs/heads/main Z
+ > remote: proc-receive> option refname refs/pull/123/head Z
+ > remote: proc-receive> option old-oid <COMMIT-A> Z
+ > remote: proc-receive> option new-oid <ZERO-OID> Z
+ > remote: proc-receive> ok refs/heads/next Z
+ > remote: proc-receive> option refname refs/pull/124/head Z
+ > remote: proc-receive> option new-oid <COMMIT-A> Z
+ > remote: # post-receive hook Z
+ > remote: post-receive< <COMMIT-A> <ZERO-OID> refs/pull/123/head Z
+ > remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/topic Z
+ > remote: post-receive< <TAG-v123> <ZERO-OID> refs/tags/v123 Z
+ > remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/124/head Z
+ > To <URL/of/upstream.git>
+ > - [deleted] refs/pull/123/head
+ > <COMMIT-A>..<COMMIT-B> <COMMIT-B> -> topic
+ > - [deleted] v123
+ > * [new reference] <COMMIT-A> -> refs/pull/124/head
+ EOF
+ test_cmp expect actual &&
+
+ test_cmp_refs -C "$upstream" <<-EOF
+ <COMMIT-A> refs/heads/main
+ <COMMIT-B> refs/heads/topic
+ EOF
+'