2006-09-28 08:58:03 +04:00
|
|
|
#!/bin/sh
|
|
|
|
#
|
|
|
|
# Copyright (c) 2005 Amos Waterland
|
|
|
|
# Copyright (c) 2006 Christian Couder
|
|
|
|
#
|
|
|
|
|
|
|
|
test_description='git pack-refs should not change the branch semantic
|
|
|
|
|
|
|
|
This test runs git pack-refs and git show-ref and checks that the branch
|
|
|
|
semantic is still the same.
|
|
|
|
'
|
|
|
|
. ./test-lib.sh
|
|
|
|
|
refs: speed up is_refname_available
Our filesystem ref storage does not allow D/F conflicts; so
if "refs/heads/a/b" exists, we do not allow "refs/heads/a"
to exist (and vice versa). This falls out naturally for
loose refs, where the filesystem enforces the condition. But
for packed-refs, we have to make the check ourselves.
We do so by iterating over the entire packed-refs namespace
and checking whether each name creates a conflict. If you
have a very large number of refs, this is quite inefficient,
as you end up doing a large number of comparisons with
uninteresting bits of the ref tree (e.g., we know that all
of "refs/tags" is uninteresting in the example above, yet we
check each entry in it).
Instead, let's take advantage of the fact that we have the
packed refs stored as a trie of ref_entry structs. We can
find each component of the proposed refname as we walk
through the trie, checking for D/F conflicts as we go. For a
refname of depth N (i.e., 4 in the above example), we only
have to visit N nodes. And at each visit, we can binary
search the M names at that level, for a total complexity of
O(N lg M). ("M" is different at each level, of course, but
we can take the worst-case "M" as a bound).
In a pathological case of fetching 30,000 fresh refs into a
repository with 8.5 million refs, this dropped the time to
run "git fetch" from tens of minutes to ~30s.
This may also help smaller cases in which we check against
loose refs (which we do when renaming a ref), as we may
avoid a disk access for unrelated loose directories.
Note that the tests we add appear at first glance to be
redundant with what is already in t3210. However, the early
tests are not robust; they are run with reflogs turned on,
meaning that we are not actually testing
is_refname_available at all! The operations will still fail
because the reflogs will hit D/F conflicts in the
filesystem. To get a true test, we must turn off reflogs
(but we don't want to do so for the entire script, because
the point of turning them on was to cover some other cases).
Reviewed-by: Michael Haggerty <mhagger@alum.mit.edu>
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-09-10 15:11:55 +04:00
|
|
|
test_expect_success 'enable reflogs' '
|
|
|
|
git config core.logallrefupdates true
|
|
|
|
'
|
2006-10-19 12:28:47 +04:00
|
|
|
|
2006-09-28 08:58:03 +04:00
|
|
|
test_expect_success \
|
|
|
|
'prepare a trivial repository' \
|
|
|
|
'echo Hello > A &&
|
2007-07-03 09:52:14 +04:00
|
|
|
git update-index --add A &&
|
2008-09-03 12:59:27 +04:00
|
|
|
git commit -m "Initial commit." &&
|
2007-07-03 09:52:14 +04:00
|
|
|
HEAD=$(git rev-parse --verify HEAD)'
|
2006-09-28 08:58:03 +04:00
|
|
|
|
|
|
|
SHA1=
|
|
|
|
|
|
|
|
test_expect_success \
|
|
|
|
'see if git show-ref works as expected' \
|
2007-07-03 09:52:14 +04:00
|
|
|
'git branch a &&
|
2015-12-22 18:27:45 +03:00
|
|
|
SHA1=$(cat .git/refs/heads/a) &&
|
2006-09-28 08:58:03 +04:00
|
|
|
echo "$SHA1 refs/heads/a" >expect &&
|
2007-07-03 09:52:14 +04:00
|
|
|
git show-ref a >result &&
|
2010-05-14 13:31:37 +04:00
|
|
|
test_cmp expect result'
|
2006-09-28 08:58:03 +04:00
|
|
|
|
|
|
|
test_expect_success \
|
|
|
|
'see if a branch still exists when packed' \
|
2007-07-03 09:52:14 +04:00
|
|
|
'git branch b &&
|
|
|
|
git pack-refs --all &&
|
2007-01-09 01:40:33 +03:00
|
|
|
rm -f .git/refs/heads/b &&
|
2006-09-28 08:58:03 +04:00
|
|
|
echo "$SHA1 refs/heads/b" >expect &&
|
2007-07-03 09:52:14 +04:00
|
|
|
git show-ref b >result &&
|
2010-05-14 13:31:37 +04:00
|
|
|
test_cmp expect result'
|
2006-09-28 08:58:03 +04:00
|
|
|
|
2008-02-01 12:50:53 +03:00
|
|
|
test_expect_success 'git branch c/d should barf if branch c exists' '
|
|
|
|
git branch c &&
|
2007-07-03 09:52:14 +04:00
|
|
|
git pack-refs --all &&
|
2008-02-01 12:50:53 +03:00
|
|
|
rm -f .git/refs/heads/c &&
|
2008-07-12 19:47:52 +04:00
|
|
|
test_must_fail git branch c/d
|
2008-02-01 12:50:53 +03:00
|
|
|
'
|
2006-09-28 08:58:03 +04:00
|
|
|
|
|
|
|
test_expect_success \
|
|
|
|
'see if a branch still exists after git pack-refs --prune' \
|
2007-07-03 09:52:14 +04:00
|
|
|
'git branch e &&
|
|
|
|
git pack-refs --all --prune &&
|
2006-09-28 08:58:03 +04:00
|
|
|
echo "$SHA1 refs/heads/e" >expect &&
|
2007-07-03 09:52:14 +04:00
|
|
|
git show-ref e >result &&
|
2010-05-14 13:31:37 +04:00
|
|
|
test_cmp expect result'
|
2006-09-28 08:58:03 +04:00
|
|
|
|
2008-02-01 12:50:53 +03:00
|
|
|
test_expect_success 'see if git pack-refs --prune remove ref files' '
|
|
|
|
git branch f &&
|
2007-07-03 09:52:14 +04:00
|
|
|
git pack-refs --all --prune &&
|
2008-02-01 12:50:53 +03:00
|
|
|
! test -f .git/refs/heads/f
|
|
|
|
'
|
2006-09-28 08:58:03 +04:00
|
|
|
|
2010-07-07 03:29:19 +04:00
|
|
|
test_expect_success 'see if git pack-refs --prune removes empty dirs' '
|
|
|
|
git branch r/s/t &&
|
|
|
|
git pack-refs --all --prune &&
|
|
|
|
! test -e .git/refs/heads/r
|
|
|
|
'
|
|
|
|
|
2006-09-28 08:58:03 +04:00
|
|
|
test_expect_success \
|
|
|
|
'git branch g should work when git branch g/h has been deleted' \
|
2007-07-03 09:52:14 +04:00
|
|
|
'git branch g/h &&
|
|
|
|
git pack-refs --all --prune &&
|
|
|
|
git branch -d g/h &&
|
|
|
|
git branch g &&
|
|
|
|
git pack-refs --all &&
|
|
|
|
git branch -d g'
|
2006-09-28 08:58:03 +04:00
|
|
|
|
2008-02-01 12:50:53 +03:00
|
|
|
test_expect_success 'git branch i/j/k should barf if branch i exists' '
|
|
|
|
git branch i &&
|
2007-07-03 09:52:14 +04:00
|
|
|
git pack-refs --all --prune &&
|
2008-07-12 19:47:52 +04:00
|
|
|
test_must_fail git branch i/j/k
|
2008-02-01 12:50:53 +03:00
|
|
|
'
|
2006-10-01 16:38:18 +04:00
|
|
|
|
|
|
|
test_expect_success \
|
|
|
|
'test git branch k after branch k/l/m and k/lm have been deleted' \
|
2007-07-03 09:52:14 +04:00
|
|
|
'git branch k/l &&
|
|
|
|
git branch k/lm &&
|
|
|
|
git branch -d k/l &&
|
|
|
|
git branch k/l/m &&
|
|
|
|
git branch -d k/l/m &&
|
|
|
|
git branch -d k/lm &&
|
|
|
|
git branch k'
|
2006-10-01 16:38:18 +04:00
|
|
|
|
|
|
|
test_expect_success \
|
|
|
|
'test git branch n after some branch deletion and pruning' \
|
2007-07-03 09:52:14 +04:00
|
|
|
'git branch n/o &&
|
|
|
|
git branch n/op &&
|
|
|
|
git branch -d n/o &&
|
|
|
|
git branch n/o/p &&
|
|
|
|
git branch -d n/op &&
|
|
|
|
git pack-refs --all --prune &&
|
|
|
|
git branch -d n/o/p &&
|
|
|
|
git branch n'
|
2006-10-01 16:38:18 +04:00
|
|
|
|
2008-11-05 23:55:53 +03:00
|
|
|
test_expect_success \
|
|
|
|
'see if up-to-date packed refs are preserved' \
|
|
|
|
'git branch q &&
|
|
|
|
git pack-refs --all --prune &&
|
|
|
|
git update-ref refs/heads/q refs/heads/q &&
|
|
|
|
! test -f .git/refs/heads/q'
|
|
|
|
|
2007-01-26 03:51:21 +03:00
|
|
|
test_expect_success 'pack, prune and repack' '
|
2008-09-03 12:59:27 +04:00
|
|
|
git tag foo &&
|
2007-07-03 09:52:14 +04:00
|
|
|
git pack-refs --all --prune &&
|
|
|
|
git show-ref >all-of-them &&
|
|
|
|
git pack-refs &&
|
|
|
|
git show-ref >again &&
|
2010-05-14 13:31:37 +04:00
|
|
|
test_cmp all-of-them again
|
2007-01-26 03:51:21 +03:00
|
|
|
'
|
|
|
|
|
2013-04-22 23:52:24 +04:00
|
|
|
test_expect_success 'explicit pack-refs with dangling packed reference' '
|
|
|
|
git commit --allow-empty -m "soon to be garbage-collected" &&
|
|
|
|
git pack-refs --all &&
|
|
|
|
git reset --hard HEAD^ &&
|
|
|
|
git reflog expire --expire=all --all &&
|
|
|
|
git prune --expire=all &&
|
|
|
|
git pack-refs --all 2>result &&
|
|
|
|
test_cmp /dev/null result
|
|
|
|
'
|
|
|
|
|
|
|
|
test_expect_success 'delete ref with dangling packed version' '
|
|
|
|
git checkout -b lamb &&
|
|
|
|
git commit --allow-empty -m "future garbage" &&
|
|
|
|
git pack-refs --all &&
|
|
|
|
git reset --hard HEAD^ &&
|
|
|
|
git checkout master &&
|
|
|
|
git reflog expire --expire=all --all &&
|
|
|
|
git prune --expire=all &&
|
|
|
|
git branch -d lamb 2>result &&
|
|
|
|
test_cmp /dev/null result
|
|
|
|
'
|
|
|
|
|
2013-04-22 23:52:25 +04:00
|
|
|
test_expect_success 'delete ref while another dangling packed ref' '
|
2013-04-22 23:52:24 +04:00
|
|
|
git branch lamb &&
|
|
|
|
git commit --allow-empty -m "future garbage" &&
|
|
|
|
git pack-refs --all &&
|
|
|
|
git reset --hard HEAD^ &&
|
|
|
|
git reflog expire --expire=all --all &&
|
|
|
|
git prune --expire=all &&
|
|
|
|
git branch -d lamb 2>result &&
|
|
|
|
test_cmp /dev/null result
|
|
|
|
'
|
|
|
|
|
2014-08-23 09:27:07 +04:00
|
|
|
test_expect_success 'pack ref directly below refs/' '
|
|
|
|
git update-ref refs/top HEAD &&
|
|
|
|
git pack-refs --all --prune &&
|
|
|
|
grep refs/top .git/packed-refs &&
|
|
|
|
test_path_is_missing .git/refs/top
|
|
|
|
'
|
|
|
|
|
2015-09-01 05:13:11 +03:00
|
|
|
test_expect_success 'do not pack ref in refs/bisect' '
|
|
|
|
git update-ref refs/bisect/local HEAD &&
|
|
|
|
git pack-refs --all --prune &&
|
|
|
|
! grep refs/bisect/local .git/packed-refs >/dev/null &&
|
|
|
|
test_path_is_file .git/refs/bisect/local
|
|
|
|
'
|
|
|
|
|
refs: speed up is_refname_available
Our filesystem ref storage does not allow D/F conflicts; so
if "refs/heads/a/b" exists, we do not allow "refs/heads/a"
to exist (and vice versa). This falls out naturally for
loose refs, where the filesystem enforces the condition. But
for packed-refs, we have to make the check ourselves.
We do so by iterating over the entire packed-refs namespace
and checking whether each name creates a conflict. If you
have a very large number of refs, this is quite inefficient,
as you end up doing a large number of comparisons with
uninteresting bits of the ref tree (e.g., we know that all
of "refs/tags" is uninteresting in the example above, yet we
check each entry in it).
Instead, let's take advantage of the fact that we have the
packed refs stored as a trie of ref_entry structs. We can
find each component of the proposed refname as we walk
through the trie, checking for D/F conflicts as we go. For a
refname of depth N (i.e., 4 in the above example), we only
have to visit N nodes. And at each visit, we can binary
search the M names at that level, for a total complexity of
O(N lg M). ("M" is different at each level, of course, but
we can take the worst-case "M" as a bound).
In a pathological case of fetching 30,000 fresh refs into a
repository with 8.5 million refs, this dropped the time to
run "git fetch" from tens of minutes to ~30s.
This may also help smaller cases in which we check against
loose refs (which we do when renaming a ref), as we may
avoid a disk access for unrelated loose directories.
Note that the tests we add appear at first glance to be
redundant with what is already in t3210. However, the early
tests are not robust; they are run with reflogs turned on,
meaning that we are not actually testing
is_refname_available at all! The operations will still fail
because the reflogs will hit D/F conflicts in the
filesystem. To get a true test, we must turn off reflogs
(but we don't want to do so for the entire script, because
the point of turning them on was to cover some other cases).
Reviewed-by: Michael Haggerty <mhagger@alum.mit.edu>
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-09-10 15:11:55 +04:00
|
|
|
test_expect_success 'disable reflogs' '
|
|
|
|
git config core.logallrefupdates false &&
|
|
|
|
rm -rf .git/logs
|
|
|
|
'
|
|
|
|
|
|
|
|
test_expect_success 'create packed foo/bar/baz branch' '
|
|
|
|
git branch foo/bar/baz &&
|
|
|
|
git pack-refs --all --prune &&
|
|
|
|
test_path_is_missing .git/refs/heads/foo/bar/baz &&
|
2015-07-28 01:57:08 +03:00
|
|
|
test_must_fail git reflog exists refs/heads/foo/bar/baz
|
refs: speed up is_refname_available
Our filesystem ref storage does not allow D/F conflicts; so
if "refs/heads/a/b" exists, we do not allow "refs/heads/a"
to exist (and vice versa). This falls out naturally for
loose refs, where the filesystem enforces the condition. But
for packed-refs, we have to make the check ourselves.
We do so by iterating over the entire packed-refs namespace
and checking whether each name creates a conflict. If you
have a very large number of refs, this is quite inefficient,
as you end up doing a large number of comparisons with
uninteresting bits of the ref tree (e.g., we know that all
of "refs/tags" is uninteresting in the example above, yet we
check each entry in it).
Instead, let's take advantage of the fact that we have the
packed refs stored as a trie of ref_entry structs. We can
find each component of the proposed refname as we walk
through the trie, checking for D/F conflicts as we go. For a
refname of depth N (i.e., 4 in the above example), we only
have to visit N nodes. And at each visit, we can binary
search the M names at that level, for a total complexity of
O(N lg M). ("M" is different at each level, of course, but
we can take the worst-case "M" as a bound).
In a pathological case of fetching 30,000 fresh refs into a
repository with 8.5 million refs, this dropped the time to
run "git fetch" from tens of minutes to ~30s.
This may also help smaller cases in which we check against
loose refs (which we do when renaming a ref), as we may
avoid a disk access for unrelated loose directories.
Note that the tests we add appear at first glance to be
redundant with what is already in t3210. However, the early
tests are not robust; they are run with reflogs turned on,
meaning that we are not actually testing
is_refname_available at all! The operations will still fail
because the reflogs will hit D/F conflicts in the
filesystem. To get a true test, we must turn off reflogs
(but we don't want to do so for the entire script, because
the point of turning them on was to cover some other cases).
Reviewed-by: Michael Haggerty <mhagger@alum.mit.edu>
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-09-10 15:11:55 +04:00
|
|
|
'
|
|
|
|
|
|
|
|
test_expect_success 'notice d/f conflict with existing directory' '
|
|
|
|
test_must_fail git branch foo &&
|
|
|
|
test_must_fail git branch foo/bar
|
|
|
|
'
|
|
|
|
|
|
|
|
test_expect_success 'existing directory reports concrete ref' '
|
|
|
|
test_must_fail git branch foo 2>stderr &&
|
|
|
|
grep refs/heads/foo/bar/baz stderr
|
|
|
|
'
|
|
|
|
|
|
|
|
test_expect_success 'notice d/f conflict with existing ref' '
|
|
|
|
test_must_fail git branch foo/bar/baz/extra &&
|
|
|
|
test_must_fail git branch foo/bar/baz/lots/of/extra/components
|
|
|
|
'
|
|
|
|
|
2017-07-01 21:31:08 +03:00
|
|
|
test_expect_success 'reject packed-refs with unterminated line' '
|
2017-07-01 21:31:07 +03:00
|
|
|
cp .git/packed-refs .git/packed-refs.bak &&
|
|
|
|
test_when_finished "mv .git/packed-refs.bak .git/packed-refs" &&
|
|
|
|
printf "%s" "$HEAD refs/zzzzz" >>.git/packed-refs &&
|
|
|
|
echo "fatal: unterminated line in .git/packed-refs: $HEAD refs/zzzzz" >expected_err &&
|
|
|
|
test_must_fail git for-each-ref >out 2>err &&
|
|
|
|
test_cmp expected_err err
|
|
|
|
'
|
|
|
|
|
2017-07-01 21:31:08 +03:00
|
|
|
test_expect_success 'reject packed-refs containing junk' '
|
2017-07-01 21:31:07 +03:00
|
|
|
cp .git/packed-refs .git/packed-refs.bak &&
|
|
|
|
test_when_finished "mv .git/packed-refs.bak .git/packed-refs" &&
|
|
|
|
printf "%s\n" "bogus content" >>.git/packed-refs &&
|
|
|
|
echo "fatal: unexpected line in .git/packed-refs: bogus content" >expected_err &&
|
|
|
|
test_must_fail git for-each-ref >out 2>err &&
|
|
|
|
test_cmp expected_err err
|
|
|
|
'
|
|
|
|
|
2017-07-01 21:31:08 +03:00
|
|
|
test_expect_success 'reject packed-refs with a short SHA-1' '
|
2017-07-01 21:31:07 +03:00
|
|
|
cp .git/packed-refs .git/packed-refs.bak &&
|
|
|
|
test_when_finished "mv .git/packed-refs.bak .git/packed-refs" &&
|
|
|
|
printf "%.7s %s\n" $HEAD refs/zzzzz >>.git/packed-refs &&
|
|
|
|
printf "fatal: unexpected line in .git/packed-refs: %.7s %s\n" $HEAD refs/zzzzz >expected_err &&
|
|
|
|
test_must_fail git for-each-ref >out 2>err &&
|
|
|
|
test_cmp expected_err err
|
|
|
|
'
|
|
|
|
|
lock_packed_refs(): allow retries when acquiring the packed-refs lock
Currently, there is only one attempt to acquire any lockfile, and if
the lock is held by another process, the locking attempt fails
immediately.
This is not such a limitation for loose reference files. First, they
don't take long to rewrite. Second, most reference updates have a
known "old" value, so if another process is updating a reference at
the same moment that we are trying to lock it, then probably the
expected "old" value will not longer be valid, and the update will
fail anyway.
But these arguments do not hold for packed-refs:
* The packed-refs file can be large and take significant time to
rewrite.
* Many references are stored in a single packed-refs file, so it could
be that the other process was changing a different reference than
the one that we are interested in.
Therefore, it is much more likely for there to be spurious lock
conflicts in connection to the packed-refs file, resulting in
unnecessary command failures.
So, if the first attempt to lock the packed-refs file fails, continue
retrying for a configurable length of time before giving up. The
default timeout is 1 second.
Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-05-11 13:35:26 +03:00
|
|
|
test_expect_success 'timeout if packed-refs.lock exists' '
|
|
|
|
LOCK=.git/packed-refs.lock &&
|
|
|
|
>"$LOCK" &&
|
|
|
|
test_when_finished "rm -f $LOCK" &&
|
|
|
|
test_must_fail git pack-refs --all --prune
|
|
|
|
'
|
|
|
|
|
|
|
|
test_expect_success 'retry acquiring packed-refs.lock' '
|
|
|
|
LOCK=.git/packed-refs.lock &&
|
|
|
|
>"$LOCK" &&
|
|
|
|
test_when_finished "wait; rm -f $LOCK" &&
|
|
|
|
{
|
|
|
|
( sleep 1 ; rm -f $LOCK ) &
|
|
|
|
} &&
|
|
|
|
git -c core.packedrefstimeout=3000 pack-refs --all --prune
|
|
|
|
'
|
|
|
|
|
2017-07-27 02:39:42 +03:00
|
|
|
test_expect_success SYMLINKS 'pack symlinked packed-refs' '
|
|
|
|
# First make sure that symlinking works when reading:
|
|
|
|
git update-ref refs/heads/loosy refs/heads/master &&
|
|
|
|
git for-each-ref >all-refs-before &&
|
|
|
|
mv .git/packed-refs .git/my-deviant-packed-refs &&
|
|
|
|
ln -s my-deviant-packed-refs .git/packed-refs &&
|
|
|
|
git for-each-ref >all-refs-linked &&
|
|
|
|
test_cmp all-refs-before all-refs-linked &&
|
|
|
|
git pack-refs --all --prune &&
|
|
|
|
git for-each-ref >all-refs-packed &&
|
|
|
|
test_cmp all-refs-before all-refs-packed &&
|
|
|
|
test -h .git/packed-refs &&
|
|
|
|
test "$(readlink .git/packed-refs)" = "my-deviant-packed-refs"
|
|
|
|
'
|
|
|
|
|
2006-09-28 08:58:03 +04:00
|
|
|
test_done
|