mirror of
https://github.com/git/git
synced 2024-10-28 19:25:47 +00:00

We sometimes call lock_ref_sha1_basic with REF_NODEREF to operate directly on a symbolic ref. This is used, for example, to move to a detached HEAD, or when updating the contents of HEAD via checkout or symbolic-ref. However, the first step of the function is to resolve the refname to get the "old" sha1, and we do so without telling resolve_ref_unsafe() that we are only interested in the symref. As a result, we may detect a problem there not with the symref itself, but with something it points to. The real-world example I found (and what is used in the test suite) is a HEAD pointing to a ref that cannot exist, because it would cause a directory/file conflict with other existing refs. This situation is somewhat broken, of course, as trying to _commit_ on that HEAD would fail. But it's not explicitly forbidden, and we should be able to move away from it. However, neither "git checkout" nor "git symbolic-ref" can do so. We try to take the lock on HEAD, which is pointing to a non-existent ref. We bail from resolve_ref_unsafe() with errno set to EISDIR, and the lock code thinks we are attempting to create a d/f conflict. Of course we're not. The problem is that the lock code has no idea what level we were at when we got EISDIR, so trying to diagnose or remove empty directories for HEAD is not useful. To make things even more complicated, we only get EISDIR in the loose-ref case. If the refs are packed, the resolution may "succeed", giving us the pointed-to ref in "refname", but a null oid. Later, we say "ah, the null oid means we are creating; let's make sure there is room for it", but mistakenly check against the _resolved_ refname, not the original. We can fix this by making two tweaks: 1. Call resolve_ref_unsafe() with RESOLVE_REF_NO_RECURSE when REF_NODEREF is set. This means any errors we get will be from the orig_refname, and we can act accordingly. We already do this in the REF_DELETING case, but we should do it for update, too. 2. If we do get a "refname" return from resolve_ref_unsafe(), even with RESOLVE_REF_NO_RECURSE it may be the name of the ref pointed-to by a symref. We already normalize this back to orig_refname before taking the lockfile, but we need to do so before the null_oid check. While we're rearranging the REF_NODEREF handling, we can also bump the initialization of lflags to the top of the function, where we are setting up other flags. This saves us from having yet another conditional block on REF_NODEREF just to set it later. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
132 lines
3.7 KiB
Bash
Executable file
132 lines
3.7 KiB
Bash
Executable file
#!/bin/sh
|
|
|
|
test_description='basic symbolic-ref tests'
|
|
. ./test-lib.sh
|
|
|
|
# If the tests munging HEAD fail, they can break detection of
|
|
# the git repo, meaning that further tests will operate on
|
|
# the surrounding git repo instead of the trash directory.
|
|
reset_to_sane() {
|
|
echo ref: refs/heads/foo >.git/HEAD
|
|
}
|
|
|
|
test_expect_success 'symbolic-ref writes HEAD' '
|
|
git symbolic-ref HEAD refs/heads/foo &&
|
|
echo ref: refs/heads/foo >expect &&
|
|
test_cmp expect .git/HEAD
|
|
'
|
|
|
|
test_expect_success 'symbolic-ref reads HEAD' '
|
|
echo refs/heads/foo >expect &&
|
|
git symbolic-ref HEAD >actual &&
|
|
test_cmp expect actual
|
|
'
|
|
|
|
test_expect_success 'symbolic-ref refuses non-ref for HEAD' '
|
|
test_must_fail git symbolic-ref HEAD foo
|
|
'
|
|
reset_to_sane
|
|
|
|
test_expect_success 'symbolic-ref refuses bare sha1' '
|
|
echo content >file && git add file && git commit -m one &&
|
|
test_must_fail git symbolic-ref HEAD `git rev-parse HEAD`
|
|
'
|
|
reset_to_sane
|
|
|
|
test_expect_success 'symbolic-ref deletes HEAD' '
|
|
git symbolic-ref -d HEAD &&
|
|
test_path_is_file .git/refs/heads/foo &&
|
|
test_path_is_missing .git/HEAD
|
|
'
|
|
reset_to_sane
|
|
|
|
test_expect_success 'symbolic-ref deletes dangling HEAD' '
|
|
git symbolic-ref HEAD refs/heads/missing &&
|
|
git symbolic-ref -d HEAD &&
|
|
test_path_is_missing .git/refs/heads/missing &&
|
|
test_path_is_missing .git/HEAD
|
|
'
|
|
reset_to_sane
|
|
|
|
test_expect_success 'symbolic-ref fails to delete missing FOO' '
|
|
echo "fatal: Cannot delete FOO, not a symbolic ref" >expect &&
|
|
test_must_fail git symbolic-ref -d FOO >actual 2>&1 &&
|
|
test_cmp expect actual
|
|
'
|
|
reset_to_sane
|
|
|
|
test_expect_success 'symbolic-ref fails to delete real ref' '
|
|
echo "fatal: Cannot delete refs/heads/foo, not a symbolic ref" >expect &&
|
|
test_must_fail git symbolic-ref -d refs/heads/foo >actual 2>&1 &&
|
|
test_path_is_file .git/refs/heads/foo &&
|
|
test_cmp expect actual
|
|
'
|
|
reset_to_sane
|
|
|
|
test_expect_success 'create large ref name' '
|
|
# make 256+ character ref; some systems may not handle that,
|
|
# so be gentle
|
|
long=0123456789abcdef &&
|
|
long=$long/$long/$long/$long &&
|
|
long=$long/$long/$long/$long &&
|
|
long_ref=refs/heads/$long &&
|
|
tree=$(git write-tree) &&
|
|
commit=$(echo foo | git commit-tree $tree) &&
|
|
if git update-ref $long_ref $commit; then
|
|
test_set_prereq LONG_REF
|
|
else
|
|
echo >&2 "long refs not supported"
|
|
fi
|
|
'
|
|
|
|
test_expect_success LONG_REF 'symbolic-ref can point to large ref name' '
|
|
git symbolic-ref HEAD $long_ref &&
|
|
echo $long_ref >expect &&
|
|
git symbolic-ref HEAD >actual &&
|
|
test_cmp expect actual
|
|
'
|
|
|
|
test_expect_success LONG_REF 'we can parse long symbolic ref' '
|
|
echo $commit >expect &&
|
|
git rev-parse --verify HEAD >actual &&
|
|
test_cmp expect actual
|
|
'
|
|
|
|
test_expect_success 'symbolic-ref reports failure in exit code' '
|
|
test_when_finished "rm -f .git/HEAD.lock" &&
|
|
>.git/HEAD.lock &&
|
|
test_must_fail git symbolic-ref HEAD refs/heads/whatever
|
|
'
|
|
|
|
test_expect_success 'symbolic-ref writes reflog entry' '
|
|
git checkout -b log1 &&
|
|
test_commit one &&
|
|
git checkout -b log2 &&
|
|
test_commit two &&
|
|
git checkout --orphan orphan &&
|
|
git symbolic-ref -m create HEAD refs/heads/log1 &&
|
|
git symbolic-ref -m update HEAD refs/heads/log2 &&
|
|
cat >expect <<-\EOF &&
|
|
update
|
|
create
|
|
EOF
|
|
git log --format=%gs -g >actual &&
|
|
test_cmp expect actual
|
|
'
|
|
|
|
test_expect_success 'symbolic-ref does not create ref d/f conflicts' '
|
|
git checkout -b df &&
|
|
test_commit df &&
|
|
test_must_fail git symbolic-ref refs/heads/df/conflict refs/heads/df &&
|
|
git pack-refs --all --prune &&
|
|
test_must_fail git symbolic-ref refs/heads/df/conflict refs/heads/df
|
|
'
|
|
|
|
test_expect_success 'symbolic-ref handles existing pointer to invalid name' '
|
|
head=$(git rev-parse HEAD) &&
|
|
git symbolic-ref HEAD refs/heads/outer &&
|
|
git update-ref refs/heads/outer/inner $head &&
|
|
git symbolic-ref HEAD refs/heads/unrelated
|
|
'
|
|
|
|
test_done
|