Subject: [PATCH v2 5/5] hook: add document and example for "proc-receive" hook

From: Jiang Xin <[email-masked]>
Date: Sun, 8 Mar 2020 11:38:16 -0400

From: Jiang Xin <[email-masked]>

Signed-off-by: Jiang Xin <[email-masked]>
---
 Documentation/githooks.txt           |  22 +++++
 t/t5411-proc-receive-hook.sh         |  71 ++++++++++++++++
 templates/hooks--proc-receive.sample | 119 +++++++++++++++++++++++++++
 3 files changed, 212 insertions(+)
 create mode 100755 templates/hooks--proc-receive.sample

diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 3dccab5375..1af213bad8 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -333,6 +333,28 @@ The default 'update' hook, when enabled--and with
 `hooks.allowunannotated` config option unset or set to false--prevents
 unannotated tags to be pushed.
 
+[[proc-receive]]
+proc-receive
+~~~~~~~~~~~~
+This hook is invoked by linkgit:git-receive-pack[1] when it reacts to
+special `git push` command.  According to refnames of the commands which
+`git push` sends to 'git-receive-pack', the commands will be devided
+into two groups by matching what the `receive.procReceiveRefs`
+configuration variable defines.  One group of the commands will execute
+the internal `execute_commands` function to update the corresponding
+refnames, and the other group of commands which have matching refnames
+will execute this 'proc-receive' hook to create pull requests, etc.
+If there is no `receive.procReceiveRefs` settings, this hook won't
+execute at all, and all commands are sent to the internal
+`execute_commands` function.
+
+Its exit status only determines the success or failure of the group of
+commands with special refnames, unless atomic push is in use.
+
+This hook executes once for the receive operation.  It takes no
+arguments, but gets the same information as the
+<<pre-receive,'pre-receive'>> hook does on its standard input.
+
 [[post-receive]]
 post-receive
 ~~~~~~~~~~~~
diff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh
index 6f35867e85..caedd49f2e 100755
--- a/t/t5411-proc-receive-hook.sh
+++ b/t/t5411-proc-receive-hook.sh
@@ -757,4 +757,75 @@ test_expect_success "(7) push one special ref (show environments)" '
 	test_cmp expect actual
 '
 
+test_expect_success "(8) install proc-receive from proc-receive.sample" '
+	mv $bare/hooks/pre-receive $bare/hooks/pre-receive.ok &&
+	mv $bare/hooks/post-receive $bare/hooks/post-receive.env &&
+	mv $bare/hooks/proc-receive $bare/hooks/proc-receive.env &&
+	cp ../../templates/hooks--proc-receive.sample $bare/hooks/proc-receive &&
+	chmod a+x $bare/hooks/proc-receive
+'
+
+test_expect_success "(8) proc-receive.sample: show push result" '
+	(
+		cd work &&
+		git push origin \
+			HEAD:refs/for/a/b/c/my/topic
+	) >out 2>&1 &&
+	grep "^remote:" out | sed -e "s/  *\$//g" >actual &&
+	cat >expect <<-EOF &&
+	remote: [proc-receive] *******************************************************
+	remote: [proc-receive] * Pull request #12345678901 created/updated           *
+	remote: [proc-receive] * URL: https://... ...                                *
+	remote: [proc-receive] *******************************************************
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "(8) proc-receive.sample: show debug info" '
+	(
+		cd work &&
+		git push -o debug=1 -o reviewers=user1,user2 \
+			origin \
+			HEAD:refs/for/a/b/c/my/topic
+	) >out 2>&1 &&
+	grep "^remote:" out | sed -e "s/  *\$//g" >actual &&
+	cat >expect <<-EOF &&
+	remote: [DEBUG] [proc-receive] push-option: AGIT_DEBUG=1
+	remote: [DEBUG] [proc-receive] push-option: AGIT_REVIEWERS=user1,user2
+	remote: [DEBUG] [proc-receive] command from stdin: $ZERO_OID $B refs/for/a/b/c/my/topic
+	remote: [DEBUG] [proc-receive] call API (AGIT_PR_TARGET=a/b/c, AGIT_PR_TOPIC=)...
+	remote: [DEBUG] [proc-receive] parse API result, and get AGIT_PR_ID, etc.
+	remote: [proc-receive] *******************************************************
+	remote: [proc-receive] * Pull request #12345678901 created/updated           *
+	remote: [proc-receive] * URL: https://... ...                                *
+	remote: [proc-receive] *******************************************************
+	remote: [DEBUG] [proc-receive] output kv pairs to stdout for git to parse.
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "(8) proc-receive.sample: fail to push non-exist branch" '
+	(
+		cd work &&
+		test_must_fail git push -o reviewers=user1,user2 \
+			origin \
+			HEAD:refs/for/a/b/x/my/topic
+	) >out 2>&1 &&
+	grep "^remote:" out | sed -e "s/  *\$//g" >actual &&
+	cat >expect <<-EOF &&
+	remote: [proc-receive] cannot find target branch from ref: refs/for/a/b/x/my/topic
+	EOF
+	test_cmp expect actual &&
+	(
+		cd $bare &&
+		git show-ref
+	) >actual &&
+	cat >expect <<-EOF &&
+	$A refs/heads/a/b/c
+	$A refs/heads/maint
+	$A refs/heads/master
+	EOF
+	test_cmp expect actual
+'
+
 test_done
diff --git a/templates/hooks--proc-receive.sample b/templates/hooks--proc-receive.sample
new file mode 100755
index 0000000000..31fa85ffe1
--- /dev/null
+++ b/templates/hooks--proc-receive.sample
@@ -0,0 +1,119 @@
+#!/bin/sh
+#
+# This is an  example hook script, DO NOT use it on production service.
+
+debug() {
+	case "$AGIT_DEBUG" in
+	"yes" | "true" | "1")
+		;;
+	*)
+		return
+	esac
+
+	echo >&2 "[DEBUG] $@"
+}
+
+# Parse push options
+if test -n "$GIT_PUSH_OPTION_COUNT"
+then
+	i=0
+	while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"
+	do
+		eval "value=\$GIT_PUSH_OPTION_$i"
+		i=$((i + 1))
+
+		k=$(echo ${value%=*} | tr [a-z] [A-Z])
+		v=${value#*=}
+		if test -n "$v" && test -n "$k"
+		then
+			k="AGIT_$k"
+		else
+			continue
+		fi
+		eval "$k=$v"
+		debug "[proc-receive] push-option: $k=$v"
+	done
+fi
+
+# Read push commands.
+count=0
+while read old new refname
+do
+	debug "[proc-receive] command from stdin: $old $new $refname"
+	count=$(( count + 1 ))
+	# Only one special refname is allowed for each push
+	if test $count -gt 1
+	then
+		echo >&2 "[proc-receive]: cannot handle more than one push commands"
+		exit 1
+	fi
+
+	# Parse refname, and set envrionment
+	remains=
+	if test "${refname#refs/for/}" != "$refname"
+	then
+		AGIT_PR_IS_DRAFT=false
+		remains=${refname#refs/for/}
+	elif test "${refname#refs/drafts/}" != "$refname"
+	then
+		AGIT_PR_IS_DRAFT=true
+		remains=${refname#refs/drafts/}
+	else
+		echo >&2 "[proc-receive] unknown refname: $refname"
+		exit 1
+	fi
+
+	ref=
+	found_ref=
+	for i in $(echo $remains | tr "/" "\n")
+	do
+		if test -z "$ref"
+		then
+			ref=$i
+		else
+			ref=$ref/$i
+		fi
+		if git rev-parse --verify $ref -- 2>/dev/null
+		then
+			found_ref=yes
+			break
+		fi
+	done
+	if test -z "$found_ref"
+	then
+		echo >&2 "[proc-receive] cannot find target branch from ref: $refname"
+		exit 1
+	fi
+	AGIT_PR_TARGET=$ref
+	AGIT_PR_SOURCE=${remains#$ref/}
+done
+
+if test -z "$AGIT_PR_TARGET"
+then
+	echo >&2 "[proc-receive] fail to parse refname, no target found"
+	exit 1
+fi
+
+# Call API to generate code review.
+debug "[proc-receive] call API (AGIT_PR_TARGET=$AGIT_PR_TARGET, AGIT_PR_TOPIC=$AGIT_PR_TOPIC)..."
+
+# Parse result of API.
+debug "[proc-receive] parse API result, and get AGIT_PR_ID, etc."
+AGIT_PR_ID="12345678901"
+AGIT_PR_LOCAL_ID="23"
+
+# Show message.
+if test -n "$AGIT_PR_ID"
+then
+	echo >&2 "[proc-receive] *******************************************************"
+	echo >&2 "[proc-receive] * Pull request #$AGIT_PR_ID created/updated           *"
+	echo >&2 "[proc-receive] * URL: https://... ...                                *"
+	echo >&2 "[proc-receive] *******************************************************"
+fi
+
+# Show envs to stdout, and will be exported as envs for "post-receive" hook.
+debug "[proc-receive] output kv pairs to stdout for git to parse."
+echo "AGIT_PR_ID=$AGIT_PR_ID"
+echo "AGIT_PR_LOCAL_ID=$AGIT_PR_LOCAL_ID"
+
+exit 0
-- 
2.26.0.rc0.5.gb02b988a14.dirty