diff mbox series

[7/7] hook: add document and example for "execute-commands" hook

Message ID 20200304113312.34229-8-zhiyou.jx@alibaba-inc.com (mailing list archive)
State New, archived
Headers show
Series New execute-commands hook for centralized workflow | expand

Commit Message

Jiang Xin March 4, 2020, 11:33 a.m. UTC
Signed-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>
---
 Documentation/githooks.txt               |  43 ++++++++
 t/t5411-execute-commands-hook.sh         |  98 +++++++++++++++++
 templates/hooks--execute-commands.sample | 131 +++++++++++++++++++++++
 3 files changed, 272 insertions(+)
 create mode 100755 templates/hooks--execute-commands.sample
diff mbox series

Patch

diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index 3dccab5375..6c21ab6db2 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -58,6 +58,49 @@  the message file.
 The default 'applypatch-msg' hook, when enabled, runs the
 'commit-msg' hook, if the latter is enabled.
 
+execute-commands--pre-receive
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+This hook is invoked by linkgit:git-receive-pack[1] when it reacts to
+special `git push` command.  Just before starting to execute the
+external 'execute-commands' hook to make changes to the repository,
+the 'execute-commands--pre-receive' hook is invoked.  This hook has
+the same functionality as 'pre-receive' hook, while it only acts on
+special commands which 'git-push' sends to 'git-receive-pack'.
+The refnames of these spacial commands have special prefix (such as
+"refs/for/") which matches what the `receive.executeCommandsHookRefs`
+configuration variable defines.
+
+If this hook does not exist, will try to find the 'execute-commands'
+hook, and run `execute-command --pre-receive` command to do some
+checks.  Its exit status determines the success or failure of the
+update.  If the hook exits with non-zero status, won't execute any of
+the commands, no metter calling the internal `execute_commands`
+function or calling the external "execute-commands" hook.
+
+This hook executes once for the receive operation, and gets the
+information from its standard input.
+See <<pre-receive,'pre-receive'>> for details.
+
+execute-commands
+~~~~~~~~~~~~~~~~
+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.executeCommandsHookRefs`
+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 external 'execute-commands' hook to create pull
+requests, etc.
+
+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 if there are any special commands with special
+refnames.  There is no argument taken by this hook, and the push options
+(if any) and command(s) will be fed to this book by its standard input.
+See the <<pre-receive,'pre-receive'>> hook for the format of the input.
+
 pre-applypatch
 ~~~~~~~~~~~~~~
 
diff --git a/t/t5411-execute-commands-hook.sh b/t/t5411-execute-commands-hook.sh
index c8ee699773..6dd1560f9d 100755
--- a/t/t5411-execute-commands-hook.sh
+++ b/t/t5411-execute-commands-hook.sh
@@ -597,4 +597,102 @@  test_expect_success "push and show environments" '
 	test_cmp expect actual
 '
 
+test_expect_success "execute-commands.sample: new execute-commands hook from templates/execute-commands.sample" '
+	mv $bare/hooks/pre-receive $bare/hooks/pre-receive.fail &&
+	mv $bare/hooks/pre-receive.ok $bare/hooks/pre-receive &&
+	mv $bare/hooks/post-receive $bare/hooks/post-receive.env &&
+	mv $bare/hooks/post-receive.ok $bare/hooks/post-receive &&
+	mv $bare/hooks/execute-commands $bare/hooks/execute-commands.env &&
+	cp ../../templates/hooks--execute-commands.sample $bare/hooks/execute-commands &&
+	chmod a+x $bare/hooks/execute-commands
+'
+
+test_expect_success "execute-commands.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: 102939797ab91a4f201d131418d2c9d919dcdd2c
+	remote: [execute-commands] *******************************************************
+	remote: [execute-commands] * Pull request #12345678901 created/updated           *
+	remote: [execute-commands] * URL: https://... ...                                *
+	remote: [execute-commands] *******************************************************
+	remote: execute: post-receive hook
+	remote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/a/b/c/my/topic.
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "execute-commands.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] [execute-commands] push-option: AGIT_DEBUG=1
+	remote: [DEBUG] [execute-commands] push-option: AGIT_REVIEWERS=user1,user2
+	remote: [DEBUG] [execute-commands] command from stdin: 0000000000000000000000000000000000000000 ce858e653cdbf70f9955a39d73a44219e4b92e9e refs/for/a/b/c/my/topic
+	remote: 102939797ab91a4f201d131418d2c9d919dcdd2c
+	remote: [DEBUG] [execute-commands: pre-receive] check permissions...
+	remote: [DEBUG] [execute-commands] push-option: AGIT_DEBUG=1
+	remote: [DEBUG] [execute-commands] push-option: AGIT_REVIEWERS=user1,user2
+	remote: [DEBUG] [execute-commands] command from stdin: 0000000000000000000000000000000000000000 ce858e653cdbf70f9955a39d73a44219e4b92e9e refs/for/a/b/c/my/topic
+	remote: [DEBUG] [execute-commands] call API (AGIT_PR_TARGET=a/b/c, AGIT_PR_TOPIC=)...
+	remote: [DEBUG] [execute-commands] parse API result, and get AGIT_PR_ID, etc.
+	remote: [execute-commands] *******************************************************
+	remote: [execute-commands] * Pull request #12345678901 created/updated           *
+	remote: [execute-commands] * URL: https://... ...                                *
+	remote: [execute-commands] *******************************************************
+	remote: [DEBUG] [execute-commands] output kv pairs to stdout for git to parse.
+	remote: execute: post-receive hook
+	remote: >> old: 0000000000000000000000000000000000000000, new: ce858e653cdbf70f9955a39d73a44219e4b92e9e, ref: refs/for/a/b/c/my/topic.
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "execute-commands.sample: fail to push refs/for/maint" '
+	(
+		cd work &&
+		test_must_fail git push -o reviewers=user1,user2 \
+			origin \
+			HEAD:refs/for/maint/my/topic
+	) >out 2>&1 &&
+	grep "^remote:" out | sed -e "s/  *\$//g" >actual &&
+	cat >expect <<-EOF &&
+	remote: 102939797ab91a4f201d131418d2c9d919dcdd2c
+	remote: [execute-commands: pre-receive] send pull request to maint branch is not allowed
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "execute-commands.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: [execute-commands] cannot find target branch from ref: refs/for/a/b/x/my/topic
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "show refs of the repository using git-show-ref" '
+	git -C $bare show-ref >actual &&
+	cat >expect <<-EOF &&
+	102939797ab91a4f201d131418d2c9d919dcdd2c refs/heads/a/b/c
+	102939797ab91a4f201d131418d2c9d919dcdd2c refs/heads/maint
+	102939797ab91a4f201d131418d2c9d919dcdd2c refs/heads/master
+	EOF
+	test_cmp expect actual
+'
+
 test_done
diff --git a/templates/hooks--execute-commands.sample b/templates/hooks--execute-commands.sample
new file mode 100755
index 0000000000..d061984bca
--- /dev/null
+++ b/templates/hooks--execute-commands.sample
@@ -0,0 +1,131 @@ 
+#!/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 "[execute-commands] push-option: $k=$v"
+	done
+fi
+
+# Read push commands.
+count=0
+while read old new refname
+do
+	debug "[execute-commands] 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 "[execute-commands]: 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 "[execute-commands] 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 "[execute-commands] 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 "[execute-commands] fail to parse refname, no target found"
+	exit 1
+fi
+
+# pre-receive mode, used to check permissions.
+if test "$1" = "--pre-receive"
+then
+	debug "[execute-commands: pre-receive] check permissions..."
+	if test "$AGIT_PR_TARGET" = "maint"
+	then
+		echo >&2 "[execute-commands: pre-receive] send pull request to maint branch is not allowed"
+		exit 1
+	fi
+	exit 0
+fi
+
+# Call API to generate code review.
+debug "[execute-commands] call API (AGIT_PR_TARGET=$AGIT_PR_TARGET, AGIT_PR_TOPIC=$AGIT_PR_TOPIC)..."
+
+# Parse result of API.
+debug "[execute-commands] 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 "[execute-commands] *******************************************************"
+	echo >&2 "[execute-commands] * Pull request #$AGIT_PR_ID created/updated           *"
+	echo >&2 "[execute-commands] * URL: https://... ...                                *"
+	echo >&2 "[execute-commands] *******************************************************"
+fi
+
+# Show envs to stdout, and will be exported as envs for "post-receive" hook.
+debug "[execute-commands] 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