#!/bin/sh

test_description='reference transaction hooks'

GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME

. ./test-lib.sh

test_expect_success setup '
	test_commit PRE &&
	PRE_OID=$(git rev-parse PRE) &&
	test_commit POST &&
	POST_OID=$(git rev-parse POST)
'

test_expect_success 'hook allows updating ref if successful' '
	git reset --hard PRE &&
	test_hook reference-transaction <<-\EOF &&
		echo "$*" >>actual
	EOF
	cat >expect <<-EOF &&
		preparing
		prepared
		committed
	EOF
	git update-ref HEAD POST &&
	test_cmp expect actual
'

test_expect_success 'hook aborts updating ref in preparing state' '
	git reset --hard PRE &&
	test_hook reference-transaction <<-\EOF &&
		if test "$1" = preparing
		then
			exit 1
		fi
	EOF
	test_must_fail git update-ref HEAD POST 2>err &&
	test_grep "in '\''preparing'\'' phase, update aborted by the reference-transaction hook" err
'

test_expect_success 'hook aborts updating ref in prepared state' '
	git reset --hard PRE &&
	test_hook reference-transaction <<-\EOF &&
		if test "$1" = prepared
		then
			exit 1
		fi
	EOF
	test_must_fail git update-ref HEAD POST 2>err &&
	test_grep "in '\''prepared'\'' phase, update aborted by the reference-transaction hook" err
'

test_expect_success 'hook gets all queued updates in prepared state' '
	test_when_finished "rm actual" &&
	git reset --hard PRE &&
	test_hook reference-transaction <<-\EOF &&
		if test "$1" = prepared
		then
			while read -r line
			do
				printf "%s\n" "$line"
			done >actual
		fi
	EOF
	cat >expect <<-EOF &&
		$ZERO_OID $POST_OID refs/heads/main
	EOF
	git update-ref HEAD POST <<-EOF &&
		update HEAD $ZERO_OID $POST_OID
		update refs/heads/main $ZERO_OID $POST_OID
	EOF
	test_cmp expect actual
'

test_expect_success 'hook gets all queued updates in committed state' '
	test_when_finished "rm actual" &&
	git reset --hard PRE &&
	test_hook reference-transaction <<-\EOF &&
		if test "$1" = committed
		then
			while read -r line
			do
				printf "%s\n" "$line"
			done >actual
		fi
	EOF
	cat >expect <<-EOF &&
		$ZERO_OID $POST_OID refs/heads/main
	EOF
	git update-ref HEAD POST &&
	test_cmp expect actual
'

test_expect_success 'hook gets all queued updates in aborted state' '
	test_when_finished "rm actual" &&
	git reset --hard PRE &&
	test_hook reference-transaction <<-\EOF &&
		if test "$1" = aborted
		then
			while read -r line
			do
				printf "%s\n" "$line"
			done >actual
		fi
	EOF
	cat >expect <<-EOF &&
		$ZERO_OID $POST_OID HEAD
		$ZERO_OID $POST_OID refs/heads/main
	EOF
	git update-ref --stdin <<-EOF &&
		start
		update HEAD POST $ZERO_OID
		update refs/heads/main POST $ZERO_OID
		abort
	EOF
	test_cmp expect actual
'

test_expect_success 'interleaving hook calls succeed' '
	test_when_finished "rm -r target-repo.git" &&

	git init --bare target-repo.git &&

	test_hook -C target-repo.git reference-transaction <<-\EOF &&
		echo $0 "$@" >>actual
	EOF

	test_hook -C target-repo.git update <<-\EOF &&
		echo $0 "$@" >>actual
	EOF

	cat >expect <<-EOF &&
		hooks/update refs/tags/PRE $ZERO_OID $PRE_OID
		hooks/update refs/tags/POST $ZERO_OID $POST_OID
		hooks/reference-transaction preparing
		hooks/reference-transaction prepared
		hooks/reference-transaction committed
	EOF

	git push ./target-repo.git PRE POST &&
	test_cmp expect target-repo.git/actual
'

test_expect_success 'hook captures git-symbolic-ref updates' '
	test_when_finished "rm actual" &&

	test_hook reference-transaction <<-\EOF &&
		echo "$*" >>actual
		while read -r line
		do
			printf "%s\n" "$line"
		done >>actual
	EOF

	git symbolic-ref refs/heads/symref refs/heads/main &&

	cat >expect <<-EOF &&
	preparing
	$ZERO_OID ref:refs/heads/main refs/heads/symref
	prepared
	$ZERO_OID ref:refs/heads/main refs/heads/symref
	committed
	$ZERO_OID ref:refs/heads/main refs/heads/symref
	EOF

	test_cmp expect actual
'

test_expect_success 'hook gets all queued symref updates' '
	test_when_finished "rm actual" &&

	git update-ref refs/heads/branch $POST_OID &&
	git symbolic-ref refs/heads/symref refs/heads/main &&
	git symbolic-ref refs/heads/symrefd refs/heads/main &&
	git symbolic-ref refs/heads/symrefu refs/heads/main &&

	test_hook reference-transaction <<-\EOF &&
	echo "$*" >>actual
	while read -r line
	do
		printf "%s\n" "$line"
	done >>actual
	EOF

	# In the files backend, "delete" also triggers an additional transaction
	# update on the packed-refs backend, which constitutes additional reflog
	# entries.
	cat >expect <<-EOF &&
	preparing
	ref:refs/heads/main $ZERO_OID refs/heads/symref
	ref:refs/heads/main $ZERO_OID refs/heads/symrefd
	$ZERO_OID ref:refs/heads/main refs/heads/symrefc
	ref:refs/heads/main ref:refs/heads/branch refs/heads/symrefu
	EOF

	if test_have_prereq REFFILES
	then
		cat >>expect <<-EOF
		aborted
		$ZERO_OID $ZERO_OID refs/heads/symrefd
		EOF
	fi &&

	cat >>expect <<-EOF &&
	prepared
	ref:refs/heads/main $ZERO_OID refs/heads/symref
	ref:refs/heads/main $ZERO_OID refs/heads/symrefd
	$ZERO_OID ref:refs/heads/main refs/heads/symrefc
	ref:refs/heads/main ref:refs/heads/branch refs/heads/symrefu
	committed
	ref:refs/heads/main $ZERO_OID refs/heads/symref
	ref:refs/heads/main $ZERO_OID refs/heads/symrefd
	$ZERO_OID ref:refs/heads/main refs/heads/symrefc
	ref:refs/heads/main ref:refs/heads/branch refs/heads/symrefu
	EOF

	git update-ref --no-deref --stdin <<-EOF &&
	start
	symref-verify refs/heads/symref refs/heads/main
	symref-delete refs/heads/symrefd refs/heads/main
	symref-create refs/heads/symrefc refs/heads/main
	symref-update refs/heads/symrefu refs/heads/branch ref refs/heads/main
	prepare
	commit
	EOF
	test_cmp expect actual
'

test_done
