diff --git a/t/t2501-cwd-empty.sh b/t/t2501-cwd-empty.sh new file mode 100755 index 0000000000..a05abd1818 --- /dev/null +++ b/t/t2501-cwd-empty.sh @@ -0,0 +1,342 @@ +#!/bin/sh + +test_description='Test handling of the current working directory becoming empty' + +. ./test-lib.sh + +test_expect_success setup ' + test_commit init && + + git branch fd_conflict && + + mkdir -p foo/bar && + test_commit foo/bar/baz && + + git revert HEAD && + git tag reverted && + + git checkout fd_conflict && + mkdir dirORfile && + test_commit dirORfile/foo && + + git rm -r dirORfile && + echo not-a-directory >dirORfile && + git add dirORfile && + git commit -m dirORfile && + + git switch -c df_conflict HEAD~1 && + test_commit random_file && + + git switch -c undo_fd_conflict fd_conflict && + git revert HEAD +' + +test_incidental_dir_removal () { + works=$1 && + shift && + + test_when_finished "git reset --hard" && + + git checkout foo/bar/baz^{commit} && + test_path_is_dir foo/bar && + + ( + cd foo && + "$@" && + + # Although we want pwd & git status to pass, test for existing + # rather than desired behavior. + if test "$works" = "success" + then + test-tool getcwd && + git status --porcelain + else + ! test-tool getcwd && + test_might_fail git status --porcelain + fi + ) && + test_path_is_missing foo/bar/baz && + test_path_is_missing foo/bar && + + # Although we want dir to be present, test for existing rather + # than desired behavior. + if test "$works" = "success" + then + test_path_is_dir foo + else + test_path_is_missing foo + fi +} + +test_required_dir_removal () { + works=$1 && + shift && + + git checkout df_conflict^{commit} && + test_when_finished "git clean -fdx" && + + ( + cd dirORfile && + + # We'd like for the command to fail (much as it would if there + # was an untracked file there), and for the index and worktree + # to be left clean with pwd and git status working afterwards. + # But test for existing rather than desired behavior. + if test "$works" = "success" + then + test_must_fail "$@" 2>../error && + grep "Refusing to remove.*current working directory" ../error && + + git diff --exit-code HEAD && + + test-tool getcwd && + git status --porcelain + else + "$@" && + ! test-tool getcwd && + test_might_fail git status --porcelain + fi + ) && + + # Although we want dirORfile to be present, test for existing rather + # than desired behavior. + if test "$works" = "success" + then + test_path_is_dir dirORfile + else + test_path_is_file dirORfile + fi +} + +test_expect_success 'checkout does not clean cwd incidentally' ' + test_incidental_dir_removal failure git checkout init +' + +test_expect_success 'checkout fails if cwd needs to be removed' ' + test_required_dir_removal failure git checkout fd_conflict +' + +test_expect_success 'reset --hard does not clean cwd incidentally' ' + test_incidental_dir_removal failure git reset --hard init +' + +test_expect_success 'reset --hard fails if cwd needs to be removed' ' + test_required_dir_removal failure git reset --hard fd_conflict +' + +test_expect_success 'merge does not clean cwd incidentally' ' + test_incidental_dir_removal failure git merge reverted +' + +# This file uses some simple merges where +# Base: 'dirORfile/' exists +# Side1: random other file changed +# Side2: 'dirORfile/' removed, 'dirORfile' added +# this should resolve cleanly, but merge-recursive throws merge conflicts +# because it's dumb. Add a special test for checking merge-recursive (and +# merge-ort), then after this just hard require ort for all remaining tests. +# +test_expect_success 'merge fails if cwd needs to be removed; recursive friendly' ' + git checkout foo/bar/baz && + test_when_finished "git clean -fdx" && + + mkdir dirORfile && + ( + cd dirORfile && + + # We would rather this failed, but we test for existing + # rather than desired behavior + git merge fd_conflict 2>../error + ) && + + ## Here is the behavior we would rather have: + #test_path_is_dir dirORfile && + #grep "Refusing to remove the current working directory" error + ## But instead we test for existing behavior + test_path_is_file dirORfile && + test_must_be_empty error +' + +GIT_TEST_MERGE_ALGORITHM=ort + +test_expect_success 'merge fails if cwd needs to be removed' ' + test_required_dir_removal failure git merge fd_conflict +' + +test_expect_success 'cherry-pick does not clean cwd incidentally' ' + test_incidental_dir_removal failure git cherry-pick reverted +' + +test_expect_success 'cherry-pick fails if cwd needs to be removed' ' + test_required_dir_removal failure git cherry-pick fd_conflict +' + +test_expect_success 'rebase does not clean cwd incidentally' ' + test_incidental_dir_removal failure git rebase reverted +' + +test_expect_success 'rebase fails if cwd needs to be removed' ' + test_required_dir_removal failure git rebase fd_conflict +' + +test_expect_success 'revert does not clean cwd incidentally' ' + test_incidental_dir_removal failure git revert HEAD +' + +test_expect_success 'revert fails if cwd needs to be removed' ' + test_required_dir_removal failure git revert undo_fd_conflict +' + +test_expect_success 'rm does not clean cwd incidentally' ' + test_incidental_dir_removal failure git rm bar/baz.t +' + +test_expect_success 'apply does not remove cwd incidentally' ' + git diff HEAD HEAD~1 >patch && + test_incidental_dir_removal failure git apply ../patch +' + +test_incidental_untracked_dir_removal () { + works=$1 && + shift && + + test_when_finished "git reset --hard" && + + git checkout foo/bar/baz^{commit} && + mkdir -p untracked && + mkdir empty + >untracked/random && + + ( + cd untracked && + "$@" && + + # Although we want pwd & git status to pass, test for existing + # rather than desired behavior. + if test "$works" = "success" + then + test-tool getcwd && + git status --porcelain + else + ! test-tool getcwd && + test_might_fail git status --porcelain + fi + ) && + test_path_is_missing empty && + test_path_is_missing untracked/random && + + # Although we want dir to be present, test for existing rather + # than desired behavior. + if test "$works" = "success" + then + test_path_is_dir untracked + else + test_path_is_missing untracked + fi +} + +test_expect_success 'clean does not remove cwd incidentally' ' + test_incidental_untracked_dir_removal failure \ + git -C .. clean -fd -e warnings . >warnings +' + +test_expect_success 'stash does not remove cwd incidentally' ' + test_incidental_untracked_dir_removal failure \ + git stash --include-untracked +' + +test_expect_success '`rm -rf dir` only removes a subset of dir' ' + test_when_finished "rm -rf a/" && + + mkdir -p a/b/c && + >a/b/c/untracked && + >a/b/c/tracked && + git add a/b/c/tracked && + + ( + cd a/b && + git rm -rf ../b + ) && + + test_path_is_dir a/b && + test_path_is_missing a/b/c/tracked && + test_path_is_file a/b/c/untracked +' + +test_expect_success '`rm -rf dir` even with only tracked files will remove something else' ' + test_when_finished "rm -rf a/" && + + mkdir -p a/b/c && + >a/b/c/tracked && + git add a/b/c/tracked && + + ( + cd a/b && + git rm -rf ../b + ) && + + test_path_is_missing a/b/c/tracked && + ## We would prefer if a/b was still present, though empty, since it + ## was the current working directory + #test_path_is_dir a/b + ## But the current behavior is that it not only deletes the directory + ## a/b as requested, but also goes and deletes a + test_path_is_missing a +' + +test_expect_success 'git version continues working from a deleted dir' ' + mkdir tmp && + ( + cd tmp && + rm -rf ../tmp && + git version + ) +' + +test_submodule_removal () { + path_status=$1 && + shift && + + test_status= + test "$path_status" = dir && test_status=test_must_fail + + # Actually, while path_status=dir && test_status=test_must_fail + # reflect our desired behavior, current behavior is: + path_status=missing + test_status= + + test_when_finished "git reset --hard HEAD~1" && + test_when_finished "rm -rf .git/modules/my_submodule" && + + git checkout foo/bar/baz && + + git init my_submodule && + touch my_submodule/file && + git -C my_submodule add file && + git -C my_submodule commit -m "initial commit" && + git submodule add ./my_submodule && + git commit -m "Add the submodule" && + + ( + cd my_submodule && + $test_status "$@" + ) && + + test_path_is_${path_status} my_submodule +} + +test_expect_success 'rm -r with -C leaves submodule if cwd inside' ' + test_submodule_removal dir git -C .. rm -r my_submodule/ +' + +test_expect_success 'rm -r leaves submodule if cwd inside' ' + test_submodule_removal dir \ + git --git-dir=../.git --work-tree=.. rm -r ../my_submodule/ +' + +test_expect_success 'rm -rf removes submodule even if cwd inside' ' + test_submodule_removal missing \ + git --git-dir=../.git --work-tree=.. rm -rf ../my_submodule/ +' + +test_done