git/t/t5600-clone-fail-cleanup.sh

111 lines
3 KiB
Bash
Raw Normal View History

#!/bin/sh
#
# Copyright (C) 2006 Carl D. Worth <cworth@cworth.org>
#
test_description='test git clone to cleanup after failure
This test covers the fact that if git clone fails, it should remove
the directory it created, to avoid the user having to manually
remove the directory before attempting a clone again.
Unless the directory already exists, in which case we clean up only what we
wrote.
'
2022-04-13 20:01:48 +00:00
TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
corrupt_repo () {
test_when_finished "rmdir foo/.git/objects.bak" &&
mkdir foo/.git/objects.bak/ &&
test_when_finished "mv foo/.git/objects.bak/* foo/.git/objects/" &&
mv foo/.git/objects/* foo/.git/objects.bak/
}
test_expect_success 'clone of non-existent source should fail' '
test_must_fail git clone foo bar
'
test_expect_success 'failed clone should not leave a directory' '
test_path_is_missing bar
'
test_expect_success 'create a repo to clone' '
test_create_repo foo
'
test_expect_success 'create objects in repo for later corruption' '
t5600: provide detached HEAD for corruption failures When checking how git-clone behaves when it fails, we stimulate some failures by trying to do a clone from a local repository whose objects have been removed. Because these clones use local optimizations, there's a subtle dependency in how the corruption is handled on the sending side. If upload-pack does not show us the broken refs (which it does not currently), then we see only HEAD (which is itself broken), and clone that as a detached HEAD. When we try to write the ref, we notice that we never got the object and bail. But if upload-pack _does_ show us the broken refs (which it may in a future patch), then we'll realize that HEAD is a symref and just write that. You'd think we'd fail when writing out the refs themselves, but we don't; we do a bulk write and skip the connectivity check because of our --local optimizations. For the non-bare case, we do notice the problem when we try to checkout. But for a bare repository, we unexpectedly complete the clone successfully! At first glance this may seem like a bug. But the whole point of those local optimizations is to give up some safety for speed. If you want to be careful, you should be using "--no-local", which would notice that the pack did not transfer sufficient objects. We could do that in these tests, but part of the point is for them to fail at specific moments (and indeed, we have a later test that checks for transport failure). However, we can make this less subtle and future-proof it against changes on the upload-pack side by just having an explicit detached HEAD in the corrupted repo. Now we'll fail as expected during the ref write if any ref _or_ HEAD is corrupt, whether we're --bare or not. Signed-off-by: Jeff King <peff@peff.net> Reviewed-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-09-24 18:34:04 +00:00
test_commit -C foo file &&
git -C foo checkout --detach &&
test_commit -C foo detached
'
# source repository given to git clone should be relative to the
# current path not to the target dir
test_expect_success 'clone of non-existent (relative to $PWD) source should fail' '
test_must_fail git clone ../foo baz
'
test_expect_success 'clone should work now that source exists' '
git clone foo bar
'
test_expect_success 'successful clone must leave the directory' '
test_path_is_dir bar
'
test_expect_success 'failed clone --separate-git-dir should not leave any directories' '
corrupt_repo &&
test_must_fail git clone --separate-git-dir gitdir foo worktree &&
test_path_is_missing gitdir &&
test_path_is_missing worktree
'
test_expect_success 'failed clone into empty leaves directory (vanilla)' '
mkdir -p empty &&
corrupt_repo &&
test_must_fail git clone foo empty &&
test_dir_is_empty empty
'
test_expect_success 'failed clone into empty leaves directory (bare)' '
mkdir -p empty &&
corrupt_repo &&
test_must_fail git clone --bare foo empty &&
test_dir_is_empty empty
'
test_expect_success 'failed clone into empty leaves directory (separate)' '
mkdir -p empty-git empty-wt &&
corrupt_repo &&
test_must_fail git clone --separate-git-dir empty-git foo empty-wt &&
test_dir_is_empty empty-git &&
test_dir_is_empty empty-wt
'
test_expect_success 'failed clone into empty leaves directory (separate, git)' '
mkdir -p empty-git &&
corrupt_repo &&
test_must_fail git clone --separate-git-dir empty-git foo no-wt &&
test_dir_is_empty empty-git &&
test_path_is_missing no-wt
'
test_expect_success 'failed clone into empty leaves directory (separate, wt)' '
mkdir -p empty-wt &&
corrupt_repo &&
test_must_fail git clone --separate-git-dir no-git foo empty-wt &&
test_path_is_missing no-git &&
test_dir_is_empty empty-wt
'
clone: clean up directory after transport_fetch_refs() failure git-clone started respecting errors from the transport subsystem in aab179d937 (builtin/clone.c: don't ignore transport_fetch_refs() errors, 2020-12-03). However, that commit didn't handle the cleanup of the filesystem quite right. The cleanup of the directory that cmd_clone() creates is done by an atexit() handler, which we control with a flag. It starts as JUNK_LEAVE_NONE ("clean up everything"), then progresses to JUNK_LEAVE_REPO when we know we have a valid repo but not working tree, and then finally JUNK_LEAVE_ALL when we have a successful checkout. Most errors cause us to die(), which then triggers the handler to do the right thing based on how far into cmd_clone() we got. But the checks added by aab179d937 instead set the "err" variable and then jump to a new "cleanup" label, which then returns our non-zero status. However, the code after the cleanup label includes setting the flag to JUNK_LEAVE_ALL, and so we accidentally leave the repository and working tree in place. One obvious option to fix this is to reorder the end of the function to set the flag first, before cleanup code, and put the label between them. But we can observe another small bug: the error return from transport_fetch_refs() is generally "-1", and we propagate that to the return value of cmd_clone(), which ultimately becomes the exit code of the process. And we try to avoid transmitting negative values via exit codes (only the low 8 bits are passed along as an unsigned value, though in practice for "-1" this at least retains the property that it's non-zero). Instead, let's just die(). That makes us consistent with rest of the code in the function. It does add a new "fatal:" line to the output, but I'd argue that's a good thing: - in the rare case that the transport code didn't say anything, now the user gets _some_ error message - even if the transport code said something like "error: ssh died of signal 9", it's nice to also say "fatal" to indicate that we considered that to be a show-stopper. Triggering this in the test suite turns out to be surprisingly difficult. Almost every error we'd encounter, including ones deep inside the transport code, cause us to just die() right there! However, one way is to put a fake wrapper around git-upload-pack that sends the complete packfile but exits with a failure code. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-05-19 11:17:15 +00:00
test_expect_success 'transport failure cleans up directory' '
test_must_fail git clone --no-local \
-u "f() { git-upload-pack \"\$@\"; return 1; }; f" \
foo broken-clone &&
test_path_is_missing broken-clone
'
test_done