clone: better handle symlinked files at .git/objects/

There is currently an odd behaviour when locally cloning a repository
with symlinks at .git/objects: using --no-hardlinks all symlinks are
dereferenced but without it, Git will try to hardlink the files with the
link() function, which has an OS-specific behaviour on symlinks. On OSX
and NetBSD, it creates a hardlink to the file pointed by the symlink
whilst on GNU/Linux, it creates a hardlink to the symlink itself.

On Manjaro GNU/Linux:
    $ touch a
    $ ln -s a b
    $ link b c
    $ ls -li a b c
    155 [...] a
    156 [...] b -> a
    156 [...] c -> a

But on NetBSD:
    $ ls -li a b c
    2609160 [...] a
    2609164 [...] b -> a
    2609160 [...] c

It's not good to have the result of a local clone to be OS-dependent and
besides that, the current behaviour on GNU/Linux may result in broken
symlinks. So let's standardize this by making the hardlinks always point
to dereferenced paths, instead of the symlinks themselves. Also, add
tests for symlinked files at .git/objects/.

Note: Git won't create symlinks at .git/objects itself, but it's better
to handle this case and be friendly with users who manually create them.

Signed-off-by: Matheus Tavares <matheus.bernardino@usp.br>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Co-authored-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Matheus Tavares 2019-07-10 20:58:56 -03:00 коммит произвёл Junio C Hamano
Родитель 0315616927
Коммит 36596fd2df
2 изменённых файлов: 21 добавлений и 8 удалений

Просмотреть файл

@ -442,7 +442,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
if (unlink(dest->buf) && errno != ENOENT)
die_errno(_("failed to unlink '%s'"), dest->buf);
if (!option_no_hardlinks) {
if (!link(src->buf, dest->buf))
if (!link(real_path(src->buf), dest->buf))
continue;
if (option_local > 0)
die_errno(_("failed to create link '%s'"), dest->buf);

Просмотреть файл

@ -266,7 +266,7 @@ test_expect_success 'clone a repo with garbage in objects/*' '
test_cmp expected actual
'
test_expect_success SYMLINKS 'setup repo with manually symlinked dirs and unknown files at objects/' '
test_expect_success SYMLINKS 'setup repo with manually symlinked or unknown files at objects/' '
git init T &&
(
cd T &&
@ -280,10 +280,19 @@ test_expect_success SYMLINKS 'setup repo with manually symlinked dirs and unknow
ln -s packs pack &&
find ?? -type d >loose-dirs &&
last_loose=$(tail -n 1 loose-dirs) &&
rm -f loose-dirs &&
mv $last_loose a-loose-dir &&
ln -s a-loose-dir $last_loose &&
first_loose=$(head -n 1 loose-dirs) &&
rm -f loose-dirs &&
cd $first_loose &&
obj=$(ls *) &&
mv $obj ../an-object &&
ln -s ../an-object $obj &&
cd ../ &&
find . -type f | sort >../../../T.objects-files.raw &&
find . -type l | sort >../../../T.objects-symlinks.raw &&
echo unknown_content >unknown_file
) &&
git -C T fsck &&
@ -291,7 +300,7 @@ test_expect_success SYMLINKS 'setup repo with manually symlinked dirs and unknow
'
test_expect_success SYMLINKS 'clone repo with symlinked dirs and unknown files at objects/' '
test_expect_success SYMLINKS 'clone repo with symlinked or unknown files at objects/' '
for option in --local --no-hardlinks --shared --dissociate
do
git clone $option T T$option || return 1 &&
@ -300,7 +309,8 @@ test_expect_success SYMLINKS 'clone repo with symlinked dirs and unknown files a
test_cmp T.objects T$option.objects &&
(
cd T$option/.git/objects &&
find . -type f | sort >../../../T$option.objects-files.raw
find . -type f | sort >../../../T$option.objects-files.raw &&
find . -type l | sort >../../../T$option.objects-symlinks.raw
)
done &&
@ -314,6 +324,7 @@ test_expect_success SYMLINKS 'clone repo with symlinked dirs and unknown files a
./Y/Z
./Y/Z
./a-loose-dir/Z
./an-object
./Y/Z
./info/packs
./pack/pack-Z.idx
@ -323,13 +334,15 @@ test_expect_success SYMLINKS 'clone repo with symlinked dirs and unknown files a
./unknown_file
EOF
for option in --local --dissociate --no-hardlinks
for option in --local --no-hardlinks --dissociate
do
test_cmp expected-files T$option.objects-files.raw.de-sha || return 1
test_cmp expected-files T$option.objects-files.raw.de-sha || return 1 &&
test_must_be_empty T$option.objects-symlinks.raw.de-sha || return 1
done &&
echo ./info/alternates >expected-files &&
test_cmp expected-files T--shared.objects-files.raw
test_cmp expected-files T--shared.objects-files.raw &&
test_must_be_empty T--shared.objects-symlinks.raw
'
test_done