diff mbox series

[1/9] t1416: more testcases for reference-transaction hook

Message ID 20220729101245.6469-2-worldhello.net@gmail.com (mailing list archive)
State New, archived
Headers show
Series Fix issues of reference-transaction hook for various git commands | expand

Commit Message

Jiang Xin July 29, 2022, 10:12 a.m. UTC
From: Jiang Xin <zhiyou.jx@alibaba-inc.com>

Append more testcases in t1416 for various git commands that may trigger
the "reference-transaction" hook.

In order to create a common "reference-transaction" hook, we define the
common hook path using config variable "core.hooksPath", and create the
"reference-transaction" hook in the path.

Some commands trigger the "reference-transaction" hook properly and get
the expected output. E.g.:

 * git branch <new-branch> <oid>		# create new branch
 * git clone
 * git commit [--amend]
 * git fetch
 * git merge
 * git push [--atomic]
 * git reset --hard
 * git tag [-f] <tag> <oid>			# update tag
 * git tag <new-tag> <oid>			# create new tag
 * git update-ref --stdin			# create new refs
 * git update-ref <ref> <oid>			# create new ref

But 16 testcases failed.

Some commands failed because the expected "<old-oid>" became
"<zero-oid>". E.g.:

 * git branch [-f] <ref> <new-oid>		# update branch
 * git cherry-pick <oid>
 * git rebase
 * git tag -d <tag>
 * git update-ref --stdin			# update/delete refs
 * git update-ref -d <ref>
 * git update-ref <ref> <new-oid> [<old-oid>]	# update ref

Some commands failed because the "reference-transaction committed"
command was repeated multiple times for the same changes. E.g.:

 * git cherry-pick
 * git rebase
 * git revert
 * git tag -d <tag>
 * git update-ref -d <ref>
 * git update-ref --stdin			# delete refs

Some commands should not trigger the "reference-transaction" hook
because no real changes have occurred to the repository. E.g.:

 * git gc
 * git pack-refs --all

Some commands did not execute the "reference-transaction" hook at all.
E.g.:

 * git branch -c <src> <dest>			# copy branch
 * git branch -m <old> <new>			# rename branch

Some commands ran unexpected command "reference-transaction aborted".
E.g.:

 * git branch -d <branch>			# delete branch
 * git branch -m <old> <new>			# rename branch
 * git cherr-pick <oid>
 * git rebase
 * git revert
 * git tag -d <tag>				# delete tag
 * git update-ref -d <ref> 			# delete ref

We will fix the failed testcases in later commits.

Signed-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>
---
 t/t1416-ref-transaction-hooks.sh | 1068 ++++++++++++++++++++++++++++++
 1 file changed, 1068 insertions(+)

Comments

Eric Sunshine July 30, 2022, 6:44 a.m. UTC | #1
On Fri, Jul 29, 2022 at 6:21 AM Jiang Xin <worldhello.net@gmail.com> wrote:
> Append more testcases in t1416 for various git commands that may trigger
> the "reference-transaction" hook.
> [...]
> Signed-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>
> ---
> diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh
> @@ -133,4 +133,1072 @@ test_expect_success 'interleaving hook calls succeed' '
> +# 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
> +}

Since tests call this function within an &&-chain, we should make sure
that &&-chain inside the function itself does the right thing. There
are a couple important and one (somewhat optional) minor fix needed
for this function. First, the function should manually break from the
loop and indicate failure (using `|| return 1`) if any command inside
the loop fails. Second, the `eval` is always going to return success
even if the embedded `git rev-parse` command fails. Finally, the minor
fix is that the `test ... || error ...` could be difficult for an
&&-chain linter to grok if we ever start linting function bodies. To
fix all these problems, you could perhaps write the function like
this:

    create_commits_in () {
        local repo="$1" &&
        if ! test -d "$repo"
        then
            error "Repository $repo does not exist."
        fi &&
        shift &&
        while test $# -gt 0
        do
            local name=$1 &&
            shift &&
            test_commit -C "$repo" --no-tag "$name" &&
            local rev=$(git -C "$repo" rev-parse HEAD) &&
            eval "$name=$rev" || return 1
        done
    }

Now that the function breaks out of the loop properly with `|| return
1` upon failure, it's no longer necessary to perform the directory
check at the top of the function since the call to test_commit() will
correctly fail if the directory does not exist. So, the function can
be shortened to:

    create_commits_in () {
        local repo="$1" &&
        shift &&
        while test $# -gt 0
        do
            local name=$1 &&
            shift &&
            test_commit -C "$repo" --no-tag "$name" &&
            local rev=$(git -C "$repo" rev-parse HEAD) &&
            eval $name=$rev || return 1
        done
    }

Having said all that, it almost seems overkill to build the loop into
this function considering that it sets only four shell variables in
the entire test script, so it might be simpler to drop the loop
altogether:

    create_commits_in () {
        local repo="$1" name="$2" &&
        test_commit -C "$repo" --no-tag "$name" &&
        local rev=$(git -C "$repo" rev-parse HEAD) &&
        echo $rev
    }

and change the callers to invoke it individually for each variable:

    A=$(create_commits_in base A) &&
    B=$(create_commits_in base B) &&
    C=$(create_commits_in base C) &&

or even drop the function entirely:

    test_commit -C base --no-tag A &&
    A=$(git -C base rev-parse HEAD) &&
    test_commit -C base --no-tag B &&
    B=$(git -C base rev-parse HEAD) &&
    test_commit -C base --no-tag C &&
    C=$(git -C base rev-parse HEAD) &&

though, it's a matter of taste whether that's better.

> +test_cmp_heads_and_tags () {
> +       indir= &&
> +       while test $# != 0
> +       do
> +               case "$1" in
> +               -C)
> +                       indir="$2"
> +                       shift
> +                       ;;

It wouldn't hurt to keep the &&-chain intact here in case the &&-chain
linter is some day updated to check function bodies, so:

    indir="$2" &&
    shift

> +               *)
> +                       break
> +                       ;;
> +               esac
> +               shift

Same here:

    esac &&
    shift

> +       done &&
> +       expect=${1:-expect} &&
> +       actual=${2:-actual-heads-and-tags} &&
> +       indir=${indir:+"$indir"/} &&
> +       test_path_is_file "$expect" &&
> +       test_when_finished "rm -f \"$actual\"" &&
> +       git ${indir:+ -C "$indir"} show-ref --heads --tags | \
> +               make_user_friendly_and_stable_output >"$actual" &&

The exit code from `git show-ref` is being lost down the pipe. You
also don't need the `\` after `|`.

> +       test_cmp "$expect" "$actual"
> +}
> +
> +test_expect_success 'setup git config and common reference-transaction hook' '
> +       git config --global \
> +               core.hooksPath "$HOME/test-hooks" &&

Nit: This would fit nicely on a single line; no need for the line splicing.

> +       git config --global core.abbrev 7 &&
> +       mkdir "test-hooks" &&
> +       write_script "test-hooks/reference-transaction" <<-EOF
> +               exec >>"$HOME/$HOOK_OUTPUT"
> +               printf "## Call hook: reference-transaction %9s ##\n" "\$@"
> +               while read -r line
> +               do
> +                   printf "%s\n" "\$line"

Nit This is the same as:

    echo "\$line"

> +               done
> +       EOF
> +'
> +
> +test_expect_success "update-ref: create new refs" '
> +       test_when_finished "rm -f $HOOK_OUTPUT" &&
> +
> +       cat >expect <<-EOF &&
> +               ## Call hook: reference-transaction  prepared ##

This and a bunch of other here-doc tags in subsequent tests are
missing the backslash:

    cat >expect <<-\EOF &&
Jiang Xin July 31, 2022, 3:25 a.m. UTC | #2
On Sat, Jul 30, 2022 at 2:44 PM Eric Sunshine <sunshine@sunshineco.com> wrote:

> Now that the function breaks out of the loop properly with `|| return
> 1` upon failure, it's no longer necessary to perform the directory
> check at the top of the function since the call to test_commit() will
> correctly fail if the directory does not exist. So, the function can
> be shortened to:
>
>     create_commits_in () {
>         local repo="$1" &&
>         shift &&
>         while test $# -gt 0
>         do
>             local name=$1 &&
>             shift &&
>             test_commit -C "$repo" --no-tag "$name" &&
>             local rev=$(git -C "$repo" rev-parse HEAD) &&
>             eval $name=$rev || return 1
>         done
>     }

Thanks, Eric. Appreciate for all suggestions. I prefer this version
and will use it in reroll v2.

> > +       done &&
> > +       expect=${1:-expect} &&
> > +       actual=${2:-actual-heads-and-tags} &&
> > +       indir=${indir:+"$indir"/} &&
> > +       test_path_is_file "$expect" &&
> > +       test_when_finished "rm -f \"$actual\"" &&
> > +       git ${indir:+ -C "$indir"} show-ref --heads --tags | \
> > +               make_user_friendly_and_stable_output >"$actual" &&
>
> The exit code from `git show-ref` is being lost down the pipe. You
> also don't need the `\` after `|`.

Will remove unnecessary backslash before EOL.

> > +       test_cmp "$expect" "$actual"
> > +}
> > +
> > +test_expect_success 'setup git config and common reference-transaction hook' '
> > +       git config --global \
> > +               core.hooksPath "$HOME/test-hooks" &&
>
> Nit: This would fit nicely on a single line; no need for the line splicing.

Will write in oneline.

> > +       git config --global core.abbrev 7 &&
> > +       mkdir "test-hooks" &&
> > +       write_script "test-hooks/reference-transaction" <<-EOF
> > +               exec >>"$HOME/$HOOK_OUTPUT"
> > +               printf "## Call hook: reference-transaction %9s ##\n" "\$@"
> > +               while read -r line
> > +               do
> > +                   printf "%s\n" "\$line"
>
> Nit This is the same as:
>
>     echo "\$line"

Will do.

> > +               done
> > +       EOF
> > +'
> > +
> > +test_expect_success "update-ref: create new refs" '
> > +       test_when_finished "rm -f $HOOK_OUTPUT" &&
> > +
> > +       cat >expect <<-EOF &&
> > +               ## Call hook: reference-transaction  prepared ##
>
> This and a bunch of other here-doc tags in subsequent tests are
> missing the backslash:
>
>     cat >expect <<-\EOF &&

Will add backslash unless there are variables need to be expanded.
diff mbox series

Patch

diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh
index 27731722a5..a84621516c 100755
--- a/t/t1416-ref-transaction-hooks.sh
+++ b/t/t1416-ref-transaction-hooks.sh
@@ -133,4 +133,1072 @@  test_expect_success 'interleaving hook calls succeed' '
 	test_cmp expect target-repo.git/actual
 '
 
+HOOK_OUTPUT=hook-output
+
+# 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 future changes of the commit ID.
+make_user_friendly_and_stable_output () {
+	sed \
+		-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 $C)[0-9a-f]*/<COMMIT-C>/g" \
+		-e "s/$(get_abbrev_oid $D)[0-9a-f]*/<COMMIT-D>/g" \
+		-e "s/$(get_abbrev_oid $E)[0-9a-f]*/<COMMIT-E>/g" \
+		-e "s/$(get_abbrev_oid $F)[0-9a-f]*/<COMMIT-F>/g" \
+		-e "s/$(get_abbrev_oid $G)[0-9a-f]*/<COMMIT-G>/g" \
+		-e "s/$(get_abbrev_oid $H)[0-9a-f]*/<COMMIT-H>/g" \
+		-e "s/$(get_abbrev_oid $I)[0-9a-f]*/<COMMIT-I>/g" \
+		-e "s/$ZERO_OID/<ZERO-OID>/g"
+}
+
+test_cmp_heads_and_tags () {
+	indir= &&
+	while test $# != 0
+	do
+		case "$1" in
+		-C)
+			indir="$2"
+			shift
+			;;
+		*)
+			break
+			;;
+		esac
+		shift
+	done &&
+	expect=${1:-expect} &&
+	actual=${2:-actual-heads-and-tags} &&
+	indir=${indir:+"$indir"/} &&
+	test_path_is_file "$expect" &&
+	test_when_finished "rm -f \"$actual\"" &&
+	git ${indir:+ -C "$indir"} show-ref --heads --tags | \
+		make_user_friendly_and_stable_output >"$actual" &&
+	test_cmp "$expect" "$actual"
+}
+
+test_expect_success 'setup git config and common reference-transaction hook' '
+	git config --global \
+		core.hooksPath "$HOME/test-hooks" &&
+	git config --global core.abbrev 7 &&
+	mkdir "test-hooks" &&
+	write_script "test-hooks/reference-transaction" <<-EOF
+		exec >>"$HOME/$HOOK_OUTPUT"
+		printf "## Call hook: reference-transaction %9s ##\n" "\$@"
+		while read -r line
+		do
+		    printf "%s\n" "\$line"
+		done
+	EOF
+'
+
+test_expect_success "setup base repository" '
+	test_when_finished "rm -f $HOOK_OUTPUT" &&
+
+	cat >expect <<-\EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-A> HEAD
+		<ZERO-OID> <COMMIT-A> refs/heads/main
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-A> HEAD
+		<ZERO-OID> <COMMIT-A> refs/heads/main
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-A> <COMMIT-B> HEAD
+		<COMMIT-A> <COMMIT-B> refs/heads/main
+		## Call hook: reference-transaction committed ##
+		<COMMIT-A> <COMMIT-B> HEAD
+		<COMMIT-A> <COMMIT-B> refs/heads/main
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-B> <COMMIT-C> HEAD
+		<COMMIT-B> <COMMIT-C> refs/heads/main
+		## Call hook: reference-transaction committed ##
+		<COMMIT-B> <COMMIT-C> HEAD
+		<COMMIT-B> <COMMIT-C> refs/heads/main
+	EOF
+
+	git init base &&
+	create_commits_in base A B C &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-\EOF &&
+		<COMMIT-C> refs/heads/main
+	EOF
+	test_cmp_heads_and_tags -C base expect
+'
+
+test_expect_success "update-ref: setup workdir using git-clone" '
+	test_when_finished "rm -f $HOOK_OUTPUT" &&
+
+	cat >expect <<-\EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-C> refs/remotes/origin/main
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-C> refs/remotes/origin/main
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-C> HEAD
+		<ZERO-OID> <COMMIT-C> refs/heads/main
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-C> HEAD
+		<ZERO-OID> <COMMIT-C> refs/heads/main
+	EOF
+
+	git clone base workdir &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-\EOF &&
+	<COMMIT-C> refs/heads/main
+	EOF
+	test_cmp_heads_and_tags -C workdir expect
+'
+
+test_expect_success "update-ref: create new refs" '
+	test_when_finished "rm -f $HOOK_OUTPUT" &&
+
+	cat >expect <<-EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-A> refs/heads/topic1
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-A> refs/heads/topic1
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-A> refs/heads/topic2
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-A> refs/heads/topic2
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-A> refs/heads/topic3
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-A> refs/heads/topic3
+	EOF
+
+	(
+		cd workdir &&
+		git update-ref refs/heads/topic1 $A &&
+		git update-ref refs/heads/topic2 $A &&
+		git update-ref refs/heads/topic3 $A
+	) &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-\EOF &&
+		<COMMIT-C> refs/heads/main
+		<COMMIT-A> refs/heads/topic1
+		<COMMIT-A> refs/heads/topic2
+		<COMMIT-A> refs/heads/topic3
+	EOF
+	test_cmp_heads_and_tags -C workdir expect
+'
+
+test_expect_failure "update-ref: update HEAD, a symbolic-ref" '
+	test_when_finished "git switch main; rm -f $HOOK_OUTPUT" &&
+
+	cat >expect <<-EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-A> <COMMIT-B> HEAD
+		<COMMIT-A> <COMMIT-B> refs/heads/topic1
+		## Call hook: reference-transaction committed ##
+		<COMMIT-A> <COMMIT-B> HEAD
+		<COMMIT-A> <COMMIT-B> refs/heads/topic1
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-A> HEAD
+		<COMMIT-B> <COMMIT-A> refs/heads/topic1
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-A> HEAD
+		<COMMIT-B> <COMMIT-A> refs/heads/topic1
+	EOF
+
+	(
+		cd workdir &&
+		git switch topic1 &&
+		git update-ref HEAD $B $A &&
+		git update-ref HEAD $A &&
+		git switch main
+	) &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-\EOF &&
+		<COMMIT-C> refs/heads/main
+		<COMMIT-A> refs/heads/topic1
+		<COMMIT-A> refs/heads/topic2
+		<COMMIT-A> refs/heads/topic3
+	EOF
+	test_cmp_heads_and_tags -C workdir expect
+'
+
+test_expect_failure "update-ref: call git-pack-refs to create packed_ref_store" '
+	test_when_finished "rm -f $HOOK_OUTPUT" &&
+	git -C workdir pack-refs --all &&
+	test_path_is_file workdir/.git/packed-refs &&
+	test_path_is_missing $HOOK_OUTPUT
+'
+
+test_expect_failure "update-ref: update refs already packed to .git/packed-refs" '
+	test_when_finished "rm -f $HOOK_OUTPUT" &&
+
+	cat >expect <<-EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-A> <COMMIT-B> refs/heads/topic2
+		## Call hook: reference-transaction committed ##
+		<COMMIT-A> <COMMIT-B> refs/heads/topic2
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-A> <COMMIT-C> refs/heads/topic3
+		## Call hook: reference-transaction committed ##
+		<COMMIT-A> <COMMIT-C> refs/heads/topic3
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-A> refs/heads/topic4
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-A> refs/heads/topic4
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-A> <COMMIT-C> refs/heads/topic4
+		## Call hook: reference-transaction committed ##
+		<COMMIT-A> <COMMIT-C> refs/heads/topic4
+	EOF
+
+	(
+		cd workdir &&
+		git update-ref refs/heads/topic2 $B $A &&
+		git update-ref refs/heads/topic3 $C &&
+		git update-ref refs/heads/topic4 $A &&
+		git update-ref refs/heads/topic4 $C
+	) &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-\EOF &&
+		<COMMIT-C> refs/heads/main
+		<COMMIT-A> refs/heads/topic1
+		<COMMIT-B> refs/heads/topic2
+		<COMMIT-C> refs/heads/topic3
+		<COMMIT-C> refs/heads/topic4
+	EOF
+	test_cmp_heads_and_tags -C workdir expect
+'
+
+test_expect_failure "update-ref: remove refs with mixed ref_stores" '
+	test_when_finished "rm -f $HOOK_OUTPUT" &&
+
+	cat >expect <<-EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <ZERO-OID> refs/heads/topic1
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-A> <ZERO-OID> refs/heads/topic1
+		## Call hook: reference-transaction committed ##
+		<COMMIT-A> <ZERO-OID> refs/heads/topic1
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <ZERO-OID> refs/heads/topic2
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-B> <ZERO-OID> refs/heads/topic2
+		## Call hook: reference-transaction committed ##
+		<COMMIT-B> <ZERO-OID> refs/heads/topic2
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <ZERO-OID> refs/heads/topic3
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-C> <ZERO-OID> refs/heads/topic3
+		## Call hook: reference-transaction committed ##
+		<COMMIT-C> <ZERO-OID> refs/heads/topic3
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-C> <ZERO-OID> refs/heads/topic4
+		## Call hook: reference-transaction committed ##
+		<COMMIT-C> <ZERO-OID> refs/heads/topic4
+	EOF
+
+	(
+		cd workdir &&
+		git update-ref -d refs/heads/topic1 $A &&
+		git update-ref -d refs/heads/topic2 $B &&
+		git update-ref -d refs/heads/topic3 &&
+		git update-ref -d refs/heads/topic4
+	) &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-\EOF &&
+		<COMMIT-C> refs/heads/main
+	EOF
+	test_cmp_heads_and_tags -C workdir expect
+'
+
+test_expect_success "update-ref --stdin: create new refs" '
+	test_when_finished "git -C workdir pack-refs --all; rm -f $HOOK_OUTPUT" &&
+
+	cat >expect <<-EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-A> refs/heads/topic1
+		<ZERO-OID> <COMMIT-A> refs/heads/topic2
+		<ZERO-OID> <COMMIT-A> refs/heads/topic3
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-A> refs/heads/topic1
+		<ZERO-OID> <COMMIT-A> refs/heads/topic2
+		<ZERO-OID> <COMMIT-A> refs/heads/topic3
+	EOF
+
+	(
+		cd workdir &&
+		git update-ref --stdin <<-EOF
+		create refs/heads/topic1 $A
+		create refs/heads/topic2 $A
+		create refs/heads/topic3 $A
+		EOF
+	) &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-\EOF &&
+		<COMMIT-C> refs/heads/main
+		<COMMIT-A> refs/heads/topic1
+		<COMMIT-A> refs/heads/topic2
+		<COMMIT-A> refs/heads/topic3
+	EOF
+	test_cmp_heads_and_tags -C workdir expect
+'
+
+test_expect_failure "update-ref --stdin: update refs" '
+	test_when_finished "rm -f $HOOK_OUTPUT" &&
+
+	cat >expect <<-EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-A> <COMMIT-B> refs/heads/topic2
+		<COMMIT-A> <COMMIT-C> refs/heads/topic3
+		<ZERO-OID> <COMMIT-C> refs/heads/topic4
+		## Call hook: reference-transaction committed ##
+		<COMMIT-A> <COMMIT-B> refs/heads/topic2
+		<COMMIT-A> <COMMIT-C> refs/heads/topic3
+		<ZERO-OID> <COMMIT-C> refs/heads/topic4
+	EOF
+
+	(
+		cd workdir &&
+		git update-ref --stdin <<-EOF
+		start
+		update refs/heads/topic2 $B $A
+		update refs/heads/topic3 $C
+		create refs/heads/topic4 $C
+		prepare
+		commit
+		EOF
+	) &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-\EOF &&
+		<COMMIT-C> refs/heads/main
+		<COMMIT-A> refs/heads/topic1
+		<COMMIT-B> refs/heads/topic2
+		<COMMIT-C> refs/heads/topic3
+		<COMMIT-C> refs/heads/topic4
+	EOF
+	test_cmp_heads_and_tags -C workdir expect
+'
+
+test_expect_failure "update-ref --stdin: delete refs" '
+	test_when_finished "rm -f $HOOK_OUTPUT" &&
+
+	cat >expect <<-EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <ZERO-OID> refs/heads/topic1
+		<ZERO-OID> <ZERO-OID> refs/heads/topic2
+		<ZERO-OID> <ZERO-OID> refs/heads/topic3
+		<ZERO-OID> <ZERO-OID> refs/heads/topic4
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-A> <ZERO-OID> refs/heads/topic1
+		<COMMIT-B> <ZERO-OID> refs/heads/topic2
+		<COMMIT-C> <ZERO-OID> refs/heads/topic3
+		<COMMIT-C> <ZERO-OID> refs/heads/topic4
+		## Call hook: reference-transaction committed ##
+		<COMMIT-A> <ZERO-OID> refs/heads/topic1
+		<COMMIT-B> <ZERO-OID> refs/heads/topic2
+		<COMMIT-C> <ZERO-OID> refs/heads/topic3
+		<COMMIT-C> <ZERO-OID> refs/heads/topic4
+	EOF
+
+	(
+		cd workdir &&
+		git update-ref --stdin <<-EOF
+		start
+		delete refs/heads/topic1
+		delete refs/heads/topic2 $B
+		delete refs/heads/topic3
+		delete refs/heads/topic4
+		prepare
+		commit
+		EOF
+	) &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-\EOF &&
+		<COMMIT-C> refs/heads/main
+	EOF
+	test_cmp_heads_and_tags -C workdir expect
+'
+
+test_expect_success "branch: setup workdir using git-fetch" '
+	test_when_finished "rm -f $HOOK_OUTPUT" &&
+
+	cat >expect <<-\EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-C> refs/remotes/origin/main
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-C> refs/remotes/origin/main
+	EOF
+
+	rm -rf workdir &&
+	git init workdir &&
+	git -C workdir remote add origin ../base &&
+	git -C workdir fetch origin &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-\EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-C> refs/heads/main
+		<ZERO-OID> <COMMIT-C> HEAD
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-C> refs/heads/main
+		<ZERO-OID> <COMMIT-C> HEAD
+	EOF
+
+	rm $HOOK_OUTPUT &&
+	git -C workdir switch -c main origin/main &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-\EOF &&
+	<COMMIT-C> refs/heads/main
+	EOF
+	test_cmp_heads_and_tags -C workdir expect
+'
+
+test_expect_success "branch: create new branches" '
+	test_when_finished "rm -f $HOOK_OUTPUT" &&
+
+	cat >expect <<-EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-A> refs/heads/topic1
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-A> refs/heads/topic1
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-A> refs/heads/topic2
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-A> refs/heads/topic2
+	EOF
+
+	(
+		cd workdir &&
+		git branch topic1 $A &&
+		git branch topic2 $A
+	) &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-\EOF &&
+		<COMMIT-C> refs/heads/main
+		<COMMIT-A> refs/heads/topic1
+		<COMMIT-A> refs/heads/topic2
+	EOF
+	test_cmp_heads_and_tags -C workdir expect
+'
+
+test_expect_failure "branch: call git-gc to create packed_ref_store" '
+	test_when_finished "rm -f $HOOK_OUTPUT" &&
+	git -C workdir gc &&
+	test_path_is_file workdir/.git/packed-refs &&
+	test_path_is_missing $HOOK_OUTPUT
+'
+
+test_expect_failure "branch: update refs to create loose refs" '
+	test_when_finished "rm -f $HOOK_OUTPUT" &&
+
+	cat >expect <<-EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-A> <COMMIT-B> refs/heads/topic2
+		## Call hook: reference-transaction committed ##
+		<COMMIT-A> <COMMIT-B> refs/heads/topic2
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-C> refs/heads/topic3
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-C> refs/heads/topic3
+	EOF
+
+	(
+		cd workdir &&
+		git branch -f topic2 $B &&
+		git branch topic3 $C
+	) &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-\EOF &&
+		<COMMIT-C> refs/heads/main
+		<COMMIT-A> refs/heads/topic1
+		<COMMIT-B> refs/heads/topic2
+		<COMMIT-C> refs/heads/topic3
+	EOF
+	test_cmp_heads_and_tags -C workdir expect
+'
+
+test_expect_failure "branch: copy branches" '
+	test_when_finished "rm -f $HOOK_OUTPUT" &&
+
+	cat >expect <<-EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-B> refs/heads/topic4
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-B> refs/heads/topic4
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-C> refs/heads/topic5
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-C> refs/heads/topic5
+	EOF
+
+	(
+		cd workdir &&
+		git branch -c topic2 topic4 &&
+		git branch -c topic3 topic5
+	) &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-\EOF &&
+		<COMMIT-C> refs/heads/main
+		<COMMIT-A> refs/heads/topic1
+		<COMMIT-B> refs/heads/topic2
+		<COMMIT-C> refs/heads/topic3
+		<COMMIT-B> refs/heads/topic4
+		<COMMIT-C> refs/heads/topic5
+	EOF
+	test_cmp_heads_and_tags -C workdir expect
+'
+
+test_expect_failure "branch: rename branches" '
+	test_when_finished "rm -f $HOOK_OUTPUT" &&
+
+	cat >expect <<-EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-B> <ZERO-OID> refs/heads/topic4
+		## Call hook: reference-transaction committed ##
+		<COMMIT-B> <ZERO-OID> refs/heads/topic4
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-B> refs/heads/topic6
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-B> refs/heads/topic6
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-C> <ZERO-OID> refs/heads/topic5
+		## Call hook: reference-transaction committed ##
+		<COMMIT-C> <ZERO-OID> refs/heads/topic5
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-C> refs/heads/topic7
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-C> refs/heads/topic7
+	EOF
+
+	(
+		cd workdir &&
+		git branch -m topic4 topic6 &&
+		git branch -m topic5 topic7
+	) &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-\EOF &&
+		<COMMIT-C> refs/heads/main
+		<COMMIT-A> refs/heads/topic1
+		<COMMIT-B> refs/heads/topic2
+		<COMMIT-C> refs/heads/topic3
+		<COMMIT-B> refs/heads/topic6
+		<COMMIT-C> refs/heads/topic7
+	EOF
+	test_cmp_heads_and_tags -C workdir expect
+'
+
+test_expect_failure "branch: remove branches" '
+	test_when_finished "rm -f $HOOK_OUTPUT" &&
+
+	cat >expect <<-EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <ZERO-OID> refs/heads/topic1
+		<ZERO-OID> <ZERO-OID> refs/heads/topic2
+		<ZERO-OID> <ZERO-OID> refs/heads/topic3
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-A> <ZERO-OID> refs/heads/topic1
+		<COMMIT-B> <ZERO-OID> refs/heads/topic2
+		<COMMIT-C> <ZERO-OID> refs/heads/topic3
+		## Call hook: reference-transaction committed ##
+		<COMMIT-A> <ZERO-OID> refs/heads/topic1
+		<COMMIT-B> <ZERO-OID> refs/heads/topic2
+		<COMMIT-C> <ZERO-OID> refs/heads/topic3
+	EOF
+
+	(
+		cd workdir &&
+		git branch -d topic1 topic2 topic3
+	) &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-\EOF &&
+		<COMMIT-C> refs/heads/main
+		<COMMIT-B> refs/heads/topic6
+		<COMMIT-C> refs/heads/topic7
+	EOF
+	test_cmp_heads_and_tags -C workdir expect
+'
+
+test_expect_success "tag: setup workdir using git-push" '
+	test_when_finished "rm -f $HOOK_OUTPUT" &&
+
+	cat >expect <<-EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-C> refs/heads/main
+		<ZERO-OID> <COMMIT-C> HEAD
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-C> refs/heads/main
+		<ZERO-OID> <COMMIT-C> HEAD
+	EOF
+
+	rm -rf workdir &&
+	git init workdir &&
+	git -C workdir config receive.denyCurrentBranch ignore &&
+	git -C base push ../workdir "+refs/heads/*:refs/heads/*" &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-\EOF &&
+		<COMMIT-C> refs/heads/main
+	EOF
+	test_cmp_heads_and_tags -C workdir expect &&
+
+	git -C workdir restore --staged -- . &&
+	git -C workdir restore -- .
+'
+
+test_expect_success "tag: create new tags" '
+	test_when_finished "rm -f $HOOK_OUTPUT" &&
+
+	cat >expect <<-EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-A> refs/tags/v1
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-A> refs/tags/v1
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-A> refs/tags/v2
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-A> refs/tags/v2
+	EOF
+
+	(
+		cd workdir &&
+		git tag v1 $A &&
+		git tag v2 $A
+	) &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-\EOF &&
+		<COMMIT-C> refs/heads/main
+		<COMMIT-A> refs/tags/v1
+		<COMMIT-A> refs/tags/v2
+	EOF
+	test_cmp_heads_and_tags -C workdir expect
+'
+
+test_expect_failure "tag: call git-pack-refs to create packed_ref_store" '
+	test_when_finished "rm -f $HOOK_OUTPUT" &&
+	git -C workdir pack-refs --all &&
+	test_path_is_file workdir/.git/packed-refs &&
+	test_path_is_missing $HOOK_OUTPUT
+'
+
+test_expect_success "tag: update refs to create loose refs" '
+	test_when_finished "rm -f $HOOK_OUTPUT" &&
+
+	cat >expect <<-EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-A> <COMMIT-B> refs/tags/v2
+		## Call hook: reference-transaction committed ##
+		<COMMIT-A> <COMMIT-B> refs/tags/v2
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-C> refs/tags/v3
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-C> refs/tags/v3
+	EOF
+
+	(
+		cd workdir &&
+		git tag -f v2 $B &&
+		git tag v3 $C
+	) &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-\EOF &&
+		<COMMIT-C> refs/heads/main
+		<COMMIT-A> refs/tags/v1
+		<COMMIT-B> refs/tags/v2
+		<COMMIT-C> refs/tags/v3
+	EOF
+	test_cmp_heads_and_tags -C workdir expect
+'
+
+test_expect_failure "tag: remove tags with mixed ref_stores" '
+	test_when_finished "rm -f $HOOK_OUTPUT" &&
+
+	cat >expect <<-EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <ZERO-OID> refs/tags/v1
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-A> <ZERO-OID> refs/tags/v1
+		## Call hook: reference-transaction committed ##
+		<COMMIT-A> <ZERO-OID> refs/tags/v1
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <ZERO-OID> refs/tags/v2
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-B> <ZERO-OID> refs/tags/v2
+		## Call hook: reference-transaction committed ##
+		<COMMIT-B> <ZERO-OID> refs/tags/v2
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-C> <ZERO-OID> refs/tags/v3
+		## Call hook: reference-transaction committed ##
+		<COMMIT-C> <ZERO-OID> refs/tags/v3
+	EOF
+
+	(
+		cd workdir &&
+		git tag -d v1 &&
+		git tag -d v2 &&
+		git tag -d v3
+	) &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-\EOF &&
+		<COMMIT-C> refs/heads/main
+	EOF
+	test_cmp_heads_and_tags -C workdir expect
+'
+
+test_expect_success "worktree: setup workdir using push --atomic" '
+	test_when_finished "rm -f $HOOK_OUTPUT" &&
+
+	cat >expect <<-EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-C> refs/heads/main
+		<ZERO-OID> <COMMIT-C> HEAD
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-C> refs/heads/main
+		<ZERO-OID> <COMMIT-C> HEAD
+	EOF
+
+	rm -rf workdir &&
+	git init --bare repo.git &&
+	git -C base push --atomic --mirror ../repo.git &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+	rm $HOOK_OUTPUT &&
+
+	cat >expect <<-EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-C> refs/remotes/origin/main
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-C> refs/remotes/origin/main
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-C> HEAD
+		<ZERO-OID> <COMMIT-C> refs/heads/main
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-C> HEAD
+		<ZERO-OID> <COMMIT-C> refs/heads/main
+	EOF
+
+	git clone --no-local repo.git workdir &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-\EOF &&
+		<COMMIT-C> refs/heads/main
+	EOF
+	test_cmp_heads_and_tags -C workdir expect
+'
+
+test_expect_success "worktree: topic1: commit --amend" '
+	test_when_finished "rm -f $HOOK_OUTPUT" &&
+
+	cat >expect <<-EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-C> refs/heads/topic1
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-C> refs/heads/topic1
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-C> <COMMIT-D> HEAD
+		<COMMIT-C> <COMMIT-D> refs/heads/topic1
+		## Call hook: reference-transaction committed ##
+		<COMMIT-C> <COMMIT-D> HEAD
+		<COMMIT-C> <COMMIT-D> refs/heads/topic1
+	EOF
+
+	(
+		cd workdir &&
+		git checkout -b topic1 &&
+		git commit --amend -m "C (amend)"
+	) &&
+	D=$(git -C workdir rev-parse HEAD) &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-EOF &&
+		<COMMIT-C> refs/heads/main
+		<COMMIT-D> refs/heads/topic1
+	EOF
+	test_cmp_heads_and_tags -C workdir expect
+'
+
+test_expect_success "worktree: topic2: merge" '
+	test_when_finished "rm -f $HOOK_OUTPUT" &&
+
+	cat >expect <<-EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-A> refs/heads/topic2
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-A> refs/heads/topic2
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-A> ORIG_HEAD
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-A> ORIG_HEAD
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-A> <COMMIT-E> HEAD
+		<COMMIT-A> <COMMIT-E> refs/heads/topic2
+		## Call hook: reference-transaction committed ##
+		<COMMIT-A> <COMMIT-E> HEAD
+		<COMMIT-A> <COMMIT-E> refs/heads/topic2
+	EOF
+
+	(
+		cd workdir &&
+		git checkout -b topic2 $A &&
+		git merge --no-ff main &&
+		test_path_is_file B.t &&
+		test_path_is_file C.t
+	) &&
+	E=$(git -C workdir rev-parse HEAD) &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-EOF &&
+		<COMMIT-C> refs/heads/main
+		<COMMIT-D> refs/heads/topic1
+		<COMMIT-E> refs/heads/topic2
+	EOF
+	test_cmp_heads_and_tags -C workdir expect
+'
+
+test_expect_failure "worktree: topic3: cherry-pick" '
+	test_when_finished "rm -f $HOOK_OUTPUT" &&
+
+	cat >expect <<-EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-A> refs/heads/topic3
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-A> refs/heads/topic3
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-C> CHERRY_PICK_HEAD
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-C> CHERRY_PICK_HEAD
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-A> <COMMIT-F> HEAD
+		<COMMIT-A> <COMMIT-F> refs/heads/topic3
+		## Call hook: reference-transaction committed ##
+		<COMMIT-A> <COMMIT-F> HEAD
+		<COMMIT-A> <COMMIT-F> refs/heads/topic3
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-C> <ZERO-OID> CHERRY_PICK_HEAD
+		## Call hook: reference-transaction committed ##
+		<COMMIT-C> <ZERO-OID> CHERRY_PICK_HEAD
+	EOF
+
+	(
+		cd workdir &&
+		git checkout -b topic3 $A &&
+		git cherry-pick $C &&
+		test_path_is_file C.t &&
+		test_path_is_missing B.t
+	) &&
+	F=$(git -C workdir rev-parse HEAD) &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-EOF &&
+		<COMMIT-C> refs/heads/main
+		<COMMIT-D> refs/heads/topic1
+		<COMMIT-E> refs/heads/topic2
+		<COMMIT-F> refs/heads/topic3
+	EOF
+	test_cmp_heads_and_tags -C workdir expect
+'
+
+test_expect_failure "worktree: topic4: rebase" '
+	test_when_finished "rm -f $HOOK_OUTPUT" &&
+
+	cat >expect <<-EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-A> <COMMIT-G> ORIG_HEAD
+		## Call hook: reference-transaction committed ##
+		<COMMIT-A> <COMMIT-G> ORIG_HEAD
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-G> <COMMIT-C> HEAD
+		## Call hook: reference-transaction committed ##
+		<COMMIT-G> <COMMIT-C> HEAD
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <ZERO-OID> REBASE_HEAD
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <ZERO-OID> REBASE_HEAD
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-G> CHERRY_PICK_HEAD
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-G> CHERRY_PICK_HEAD
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-C> <COMMIT-H> HEAD
+		## Call hook: reference-transaction committed ##
+		<COMMIT-C> <COMMIT-H> HEAD
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-G> <ZERO-OID> CHERRY_PICK_HEAD
+		## Call hook: reference-transaction committed ##
+		<COMMIT-G> <ZERO-OID> CHERRY_PICK_HEAD
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-G> <COMMIT-H> refs/heads/topic4
+		## Call hook: reference-transaction committed ##
+		<COMMIT-G> <COMMIT-H> refs/heads/topic4
+	EOF
+
+	git -C workdir checkout -b topic4 $A &&
+	create_commits_in workdir G &&
+	rm $HOOK_OUTPUT &&
+	git -C workdir rebase main &&
+	H=$(git -C workdir rev-parse HEAD) &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-EOF &&
+		<COMMIT-C> refs/heads/main
+		<COMMIT-D> refs/heads/topic1
+		<COMMIT-E> refs/heads/topic2
+		<COMMIT-F> refs/heads/topic3
+		<COMMIT-H> refs/heads/topic4
+	EOF
+	test_cmp_heads_and_tags -C workdir expect
+'
+
+test_expect_failure "worktree: topic5: revert" '
+	test_when_finished "rm -f $HOOK_OUTPUT" &&
+
+	cat >expect <<-EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-C> refs/heads/topic5
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-C> refs/heads/topic5
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-C> <COMMIT-I> HEAD
+		<COMMIT-C> <COMMIT-I> refs/heads/topic5
+		## Call hook: reference-transaction committed ##
+		<COMMIT-C> <COMMIT-I> HEAD
+		<COMMIT-C> <COMMIT-I> refs/heads/topic5
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <ZERO-OID> CHERRY_PICK_HEAD
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <ZERO-OID> CHERRY_PICK_HEAD
+	EOF
+
+	(
+		cd workdir &&
+		git checkout -b topic5 $C &&
+		git revert HEAD &&
+		test_path_is_file B.t &&
+		test_path_is_missing C.t
+	) &&
+	I=$(git -C workdir rev-parse HEAD) &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-EOF &&
+		<COMMIT-C> refs/heads/main
+		<COMMIT-D> refs/heads/topic1
+		<COMMIT-E> refs/heads/topic2
+		<COMMIT-F> refs/heads/topic3
+		<COMMIT-H> refs/heads/topic4
+		<COMMIT-I> refs/heads/topic5
+	EOF
+	test_cmp_heads_and_tags -C workdir expect
+'
+
+test_expect_success "worktree: topic6: reset" '
+	test_when_finished "rm -f $HOOK_OUTPUT" &&
+
+	cat >expect <<-EOF &&
+		## Call hook: reference-transaction  prepared ##
+		<ZERO-OID> <COMMIT-C> refs/heads/topic6
+		## Call hook: reference-transaction committed ##
+		<ZERO-OID> <COMMIT-C> refs/heads/topic6
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-G> <COMMIT-C> ORIG_HEAD
+		## Call hook: reference-transaction committed ##
+		<COMMIT-G> <COMMIT-C> ORIG_HEAD
+		## Call hook: reference-transaction  prepared ##
+		<COMMIT-C> <COMMIT-B> HEAD
+		<COMMIT-C> <COMMIT-B> refs/heads/topic6
+		## Call hook: reference-transaction committed ##
+		<COMMIT-C> <COMMIT-B> HEAD
+		<COMMIT-C> <COMMIT-B> refs/heads/topic6
+	EOF
+
+	(
+		cd workdir &&
+		git checkout -b topic6 $C &&
+		git reset --hard $B
+	) &&
+	make_user_friendly_and_stable_output <$HOOK_OUTPUT >actual &&
+	test_cmp expect actual &&
+
+	cat >expect <<-EOF &&
+		<COMMIT-C> refs/heads/main
+		<COMMIT-D> refs/heads/topic1
+		<COMMIT-E> refs/heads/topic2
+		<COMMIT-F> refs/heads/topic3
+		<COMMIT-H> refs/heads/topic4
+		<COMMIT-I> refs/heads/topic5
+		<COMMIT-B> refs/heads/topic6
+	EOF
+	test_cmp_heads_and_tags -C workdir expect
+'
+
 test_done