mirror of
https://github.com/git/git
synced 2024-08-27 11:39:22 +00:00
![Patrick Steinhardt](/assets/img/avatar_default.png)
When deleting refs from the loose-files refs backend, then we need to be careful to also delete the same ref from the packed refs backend, if it exists. If we don't, then deleting the loose ref would "uncover" the packed ref. We thus always have to queue up deletions of refs for both the loose and the packed refs backend. This is done in two separate transactions, where the end result is that the reference-transaction hook is executed twice for the deleted refs. This behaviour is quite misleading: it's exposing implementation details of how the files backend works to the user, in contrast to the logical updates that we'd really want to expose via the hook. Worse yet, whether the hook gets executed once or twice depends on how well-packed the repository is: if the ref only exists as a loose ref, then we execute it once, otherwise if it is also packed then we execute it twice. Fix this behaviour and don't execute the reference-transaction hook at all when refs in the packed-refs backend if it's driven by the files backend. This works as expected even in case the refs to be deleted only exist in the packed-refs backend because the loose-backend always queues refs in its own transaction even if they don't exist such that they can be locked for concurrent creation. And it also does the right thing in case neither of the backends has the ref because that would cause the transaction to fail completely. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>
190 lines
4.5 KiB
Bash
Executable file
190 lines
4.5 KiB
Bash
Executable file
#!/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 '
|
|
mkdir -p .git/hooks &&
|
|
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' '
|
|
test_when_finished "rm .git/hooks/reference-transaction" &&
|
|
git reset --hard PRE &&
|
|
write_script .git/hooks/reference-transaction <<-\EOF &&
|
|
echo "$*" >>actual
|
|
EOF
|
|
cat >expect <<-EOF &&
|
|
prepared
|
|
committed
|
|
EOF
|
|
git update-ref HEAD POST &&
|
|
test_cmp expect actual
|
|
'
|
|
|
|
test_expect_success 'hook aborts updating ref in prepared state' '
|
|
test_when_finished "rm .git/hooks/reference-transaction" &&
|
|
git reset --hard PRE &&
|
|
write_script .git/hooks/reference-transaction <<-\EOF &&
|
|
if test "$1" = prepared
|
|
then
|
|
exit 1
|
|
fi
|
|
EOF
|
|
test_must_fail git update-ref HEAD POST 2>err &&
|
|
test_i18ngrep "ref updates aborted by hook" err
|
|
'
|
|
|
|
test_expect_success 'hook gets all queued updates in prepared state' '
|
|
test_when_finished "rm .git/hooks/reference-transaction actual" &&
|
|
git reset --hard PRE &&
|
|
write_script .git/hooks/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 HEAD
|
|
$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 .git/hooks/reference-transaction actual" &&
|
|
git reset --hard PRE &&
|
|
write_script .git/hooks/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 HEAD
|
|
$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 .git/hooks/reference-transaction actual" &&
|
|
git reset --hard PRE &&
|
|
write_script .git/hooks/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 &&
|
|
|
|
write_script target-repo.git/hooks/reference-transaction <<-\EOF &&
|
|
echo $0 "$@" >>actual
|
|
EOF
|
|
|
|
write_script target-repo.git/hooks/update <<-\EOF &&
|
|
echo $0 "$@" >>actual
|
|
EOF
|
|
|
|
cat >expect <<-EOF &&
|
|
hooks/update refs/tags/PRE $ZERO_OID $PRE_OID
|
|
hooks/reference-transaction prepared
|
|
hooks/reference-transaction committed
|
|
hooks/update refs/tags/POST $ZERO_OID $POST_OID
|
|
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 does not get called on packing refs' '
|
|
# Pack references first such that we are in a known state.
|
|
git pack-refs --all &&
|
|
|
|
write_script .git/hooks/reference-transaction <<-\EOF &&
|
|
echo "$@" >>actual
|
|
cat >>actual
|
|
EOF
|
|
rm -f actual &&
|
|
|
|
git update-ref refs/heads/unpacked-ref $POST_OID &&
|
|
git pack-refs --all &&
|
|
|
|
# We only expect a single hook invocation, which is the call to
|
|
# git-update-ref(1).
|
|
cat >expect <<-EOF &&
|
|
prepared
|
|
$ZERO_OID $POST_OID refs/heads/unpacked-ref
|
|
committed
|
|
$ZERO_OID $POST_OID refs/heads/unpacked-ref
|
|
EOF
|
|
|
|
test_cmp expect actual
|
|
'
|
|
|
|
test_expect_success 'deleting packed ref calls hook once' '
|
|
# Create a reference and pack it.
|
|
git update-ref refs/heads/to-be-deleted $POST_OID &&
|
|
git pack-refs --all &&
|
|
|
|
write_script .git/hooks/reference-transaction <<-\EOF &&
|
|
echo "$@" >>actual
|
|
cat >>actual
|
|
EOF
|
|
rm -f actual &&
|
|
|
|
git update-ref -d refs/heads/to-be-deleted $POST_OID &&
|
|
|
|
# We only expect a single hook invocation, which is the logical
|
|
# deletion.
|
|
cat >expect <<-EOF &&
|
|
prepared
|
|
$POST_OID $ZERO_OID refs/heads/to-be-deleted
|
|
committed
|
|
$POST_OID $ZERO_OID refs/heads/to-be-deleted
|
|
EOF
|
|
|
|
test_cmp expect actual
|
|
'
|
|
|
|
test_done
|