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>

Matheus Tavares committed Jul 10, 2019 at 20:58 UTC 36596fd2dfa473cf1069d23776e62cc156e7b5c6
2 files changed +21 -8
builtin/clone.c
+1 -1
@@ -442,7 +442,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
442 if (unlink(dest->buf) && errno != ENOENT)
443 die_errno(_("failed to unlink '%s'"), dest->buf);
444 if (!option_no_hardlinks) {
445 - if (!link(src->buf, dest->buf))
445 + if (!link(real_path(src->buf), dest->buf))
446 continue;
447 if (option_local > 0)
448 die_errno(_("failed to create link '%s'"), dest->buf);
t/t5604-clone-reference.sh
+20 -7
@@ -266,7 +266,7 @@ test_expect_success 'clone a repo with garbage in objects/*' '
266 test_cmp expected actual
267 '
268
269 -test_expect_success SYMLINKS 'setup repo with manually symlinked dirs and unknown files at objects/' '
269 +test_expect_success SYMLINKS 'setup repo with manually symlinked or unknown files at objects/' '
270 git init T &&
271 (
272 cd T &&
@@ -280,10 +280,19 @@ test_expect_success SYMLINKS 'setup repo with manually symlinked dirs and unknow
280 ln -s packs pack &&
281 find ?? -type d >loose-dirs &&
282 last_loose=$(tail -n 1 loose-dirs) &&
283 - rm -f loose-dirs &&
283 mv $last_loose a-loose-dir &&
284 ln -s a-loose-dir $last_loose &&
285 + first_loose=$(head -n 1 loose-dirs) &&
286 + rm -f loose-dirs &&
287 +
288 + cd $first_loose &&
289 + obj=$(ls *) &&
290 + mv $obj ../an-object &&
291 + ln -s ../an-object $obj &&
292 +
293 + cd ../ &&
294 find . -type f | sort >../../../T.objects-files.raw &&
295 + find . -type l | sort >../../../T.objects-symlinks.raw &&
296 echo unknown_content >unknown_file
297 ) &&
298 git -C T fsck &&
@@ -291,7 +300,7 @@ test_expect_success SYMLINKS 'setup repo with manually symlinked dirs and unknow
300 '
301
302
294 -test_expect_success SYMLINKS 'clone repo with symlinked dirs and unknown files at objects/' '
303 +test_expect_success SYMLINKS 'clone repo with symlinked or unknown files at objects/' '
304 for option in --local --no-hardlinks --shared --dissociate
305 do
306 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
309 test_cmp T.objects T$option.objects &&
310 (
311 cd T$option/.git/objects &&
303 - find . -type f | sort >../../../T$option.objects-files.raw
312 + find . -type f | sort >../../../T$option.objects-files.raw &&
313 + find . -type l | sort >../../../T$option.objects-symlinks.raw
314 )
315 done &&
316
@@ -314,6 +324,7 @@ test_expect_success SYMLINKS 'clone repo with symlinked dirs and unknown files a
324 ./Y/Z
325 ./Y/Z
326 ./a-loose-dir/Z
327 + ./an-object
328 ./Y/Z
329 ./info/packs
330 ./pack/pack-Z.idx
@@ -323,13 +334,15 @@ test_expect_success SYMLINKS 'clone repo with symlinked dirs and unknown files a
334 ./unknown_file
335 EOF
336
326 - for option in --local --dissociate --no-hardlinks
337 + for option in --local --no-hardlinks --dissociate
338 do
328 - test_cmp expected-files T$option.objects-files.raw.de-sha || return 1
339 + test_cmp expected-files T$option.objects-files.raw.de-sha || return 1 &&
340 + test_must_be_empty T$option.objects-symlinks.raw.de-sha || return 1
341 done &&
342
343 echo ./info/alternates >expected-files &&
332 - test_cmp expected-files T--shared.objects-files.raw
344 + test_cmp expected-files T--shared.objects-files.raw &&
345 + test_must_be_empty T--shared.objects-symlinks.raw
346 '
347
348 test_done