Skip to content

Commit 8a0d52d

Browse files
newrengitster
authored andcommitted
t2501: add various tests for removing the current working directory
Numerous commands will remove directories left empty as a "convenience" after removing files within them. That is normally fine, but removing the current working directory can be rather inconvenient since it can cause confusion for the user when they run subsequent commands. For example, after one git process has removed the current working directory, git status/log/diff will all abort with the message: fatal: Unable to read current working directory: No such file or directory We also have code paths that, when a file needs to be placed where a directory is (due to e.g. checkout, merge, reset, whatever), will check if this is okay and error out if not. These rules include: * all tracked files under that directory are intended to be removed by the operation * none of the tracked files under that directory have uncommitted modification * there are no untracked files under that directory However, if we end up remove the current working directory, we can cause user confusion when they run subsequent commands, so we would prefer if there was a fourth rule added to this list: avoid removing the current working directory. Since there are several code paths that can result in the current working directory being removed, add several tests of various different codepaths. To make it clearer what the difference between the current behavior and the behavior at the end of the series, code both of them into the tests and have the appropriate behavior be selected by a flag. Subsequent commits will toggle the flag from current to desired behavior. Also add a few tests suggested during the review of earlier rounds of this patch series. Acked-by: Derrick Stolee <[email protected]> Acked-by: Ævar Arnfjörð Bjarmason <[email protected]> Signed-off-by: Elijah Newren <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent cd3e606 commit 8a0d52d

File tree

1 file changed

+342
-0
lines changed

1 file changed

+342
-0
lines changed

t/t2501-cwd-empty.sh

Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
#!/bin/sh
2+
3+
test_description='Test handling of the current working directory becoming empty'
4+
5+
. ./test-lib.sh
6+
7+
test_expect_success setup '
8+
test_commit init &&
9+
10+
git branch fd_conflict &&
11+
12+
mkdir -p foo/bar &&
13+
test_commit foo/bar/baz &&
14+
15+
git revert HEAD &&
16+
git tag reverted &&
17+
18+
git checkout fd_conflict &&
19+
mkdir dirORfile &&
20+
test_commit dirORfile/foo &&
21+
22+
git rm -r dirORfile &&
23+
echo not-a-directory >dirORfile &&
24+
git add dirORfile &&
25+
git commit -m dirORfile &&
26+
27+
git switch -c df_conflict HEAD~1 &&
28+
test_commit random_file &&
29+
30+
git switch -c undo_fd_conflict fd_conflict &&
31+
git revert HEAD
32+
'
33+
34+
test_incidental_dir_removal () {
35+
works=$1 &&
36+
shift &&
37+
38+
test_when_finished "git reset --hard" &&
39+
40+
git checkout foo/bar/baz^{commit} &&
41+
test_path_is_dir foo/bar &&
42+
43+
(
44+
cd foo &&
45+
"$@" &&
46+
47+
# Although we want pwd & git status to pass, test for existing
48+
# rather than desired behavior.
49+
if test "$works" = "success"
50+
then
51+
test-tool getcwd &&
52+
git status --porcelain
53+
else
54+
! test-tool getcwd &&
55+
test_might_fail git status --porcelain
56+
fi
57+
) &&
58+
test_path_is_missing foo/bar/baz &&
59+
test_path_is_missing foo/bar &&
60+
61+
# Although we want dir to be present, test for existing rather
62+
# than desired behavior.
63+
if test "$works" = "success"
64+
then
65+
test_path_is_dir foo
66+
else
67+
test_path_is_missing foo
68+
fi
69+
}
70+
71+
test_required_dir_removal () {
72+
works=$1 &&
73+
shift &&
74+
75+
git checkout df_conflict^{commit} &&
76+
test_when_finished "git clean -fdx" &&
77+
78+
(
79+
cd dirORfile &&
80+
81+
# We'd like for the command to fail (much as it would if there
82+
# was an untracked file there), and for the index and worktree
83+
# to be left clean with pwd and git status working afterwards.
84+
# But test for existing rather than desired behavior.
85+
if test "$works" = "success"
86+
then
87+
test_must_fail "$@" 2>../error &&
88+
grep "Refusing to remove.*current working directory" ../error &&
89+
90+
git diff --exit-code HEAD &&
91+
92+
test-tool getcwd &&
93+
git status --porcelain
94+
else
95+
"$@" &&
96+
! test-tool getcwd &&
97+
test_might_fail git status --porcelain
98+
fi
99+
) &&
100+
101+
# Although we want dirORfile to be present, test for existing rather
102+
# than desired behavior.
103+
if test "$works" = "success"
104+
then
105+
test_path_is_dir dirORfile
106+
else
107+
test_path_is_file dirORfile
108+
fi
109+
}
110+
111+
test_expect_success 'checkout does not clean cwd incidentally' '
112+
test_incidental_dir_removal failure git checkout init
113+
'
114+
115+
test_expect_success 'checkout fails if cwd needs to be removed' '
116+
test_required_dir_removal failure git checkout fd_conflict
117+
'
118+
119+
test_expect_success 'reset --hard does not clean cwd incidentally' '
120+
test_incidental_dir_removal failure git reset --hard init
121+
'
122+
123+
test_expect_success 'reset --hard fails if cwd needs to be removed' '
124+
test_required_dir_removal failure git reset --hard fd_conflict
125+
'
126+
127+
test_expect_success 'merge does not clean cwd incidentally' '
128+
test_incidental_dir_removal failure git merge reverted
129+
'
130+
131+
# This file uses some simple merges where
132+
# Base: 'dirORfile/' exists
133+
# Side1: random other file changed
134+
# Side2: 'dirORfile/' removed, 'dirORfile' added
135+
# this should resolve cleanly, but merge-recursive throws merge conflicts
136+
# because it's dumb. Add a special test for checking merge-recursive (and
137+
# merge-ort), then after this just hard require ort for all remaining tests.
138+
#
139+
test_expect_success 'merge fails if cwd needs to be removed; recursive friendly' '
140+
git checkout foo/bar/baz &&
141+
test_when_finished "git clean -fdx" &&
142+
143+
mkdir dirORfile &&
144+
(
145+
cd dirORfile &&
146+
147+
# We would rather this failed, but we test for existing
148+
# rather than desired behavior
149+
git merge fd_conflict 2>../error
150+
) &&
151+
152+
## Here is the behavior we would rather have:
153+
#test_path_is_dir dirORfile &&
154+
#grep "Refusing to remove the current working directory" error
155+
## But instead we test for existing behavior
156+
test_path_is_file dirORfile &&
157+
test_must_be_empty error
158+
'
159+
160+
GIT_TEST_MERGE_ALGORITHM=ort
161+
162+
test_expect_success 'merge fails if cwd needs to be removed' '
163+
test_required_dir_removal failure git merge fd_conflict
164+
'
165+
166+
test_expect_success 'cherry-pick does not clean cwd incidentally' '
167+
test_incidental_dir_removal failure git cherry-pick reverted
168+
'
169+
170+
test_expect_success 'cherry-pick fails if cwd needs to be removed' '
171+
test_required_dir_removal failure git cherry-pick fd_conflict
172+
'
173+
174+
test_expect_success 'rebase does not clean cwd incidentally' '
175+
test_incidental_dir_removal failure git rebase reverted
176+
'
177+
178+
test_expect_success 'rebase fails if cwd needs to be removed' '
179+
test_required_dir_removal failure git rebase fd_conflict
180+
'
181+
182+
test_expect_success 'revert does not clean cwd incidentally' '
183+
test_incidental_dir_removal failure git revert HEAD
184+
'
185+
186+
test_expect_success 'revert fails if cwd needs to be removed' '
187+
test_required_dir_removal failure git revert undo_fd_conflict
188+
'
189+
190+
test_expect_success 'rm does not clean cwd incidentally' '
191+
test_incidental_dir_removal failure git rm bar/baz.t
192+
'
193+
194+
test_expect_success 'apply does not remove cwd incidentally' '
195+
git diff HEAD HEAD~1 >patch &&
196+
test_incidental_dir_removal failure git apply ../patch
197+
'
198+
199+
test_incidental_untracked_dir_removal () {
200+
works=$1 &&
201+
shift &&
202+
203+
test_when_finished "git reset --hard" &&
204+
205+
git checkout foo/bar/baz^{commit} &&
206+
mkdir -p untracked &&
207+
mkdir empty
208+
>untracked/random &&
209+
210+
(
211+
cd untracked &&
212+
"$@" &&
213+
214+
# Although we want pwd & git status to pass, test for existing
215+
# rather than desired behavior.
216+
if test "$works" = "success"
217+
then
218+
test-tool getcwd &&
219+
git status --porcelain
220+
else
221+
! test-tool getcwd &&
222+
test_might_fail git status --porcelain
223+
fi
224+
) &&
225+
test_path_is_missing empty &&
226+
test_path_is_missing untracked/random &&
227+
228+
# Although we want dir to be present, test for existing rather
229+
# than desired behavior.
230+
if test "$works" = "success"
231+
then
232+
test_path_is_dir untracked
233+
else
234+
test_path_is_missing untracked
235+
fi
236+
}
237+
238+
test_expect_success 'clean does not remove cwd incidentally' '
239+
test_incidental_untracked_dir_removal failure \
240+
git -C .. clean -fd -e warnings . >warnings
241+
'
242+
243+
test_expect_success 'stash does not remove cwd incidentally' '
244+
test_incidental_untracked_dir_removal failure \
245+
git stash --include-untracked
246+
'
247+
248+
test_expect_success '`rm -rf dir` only removes a subset of dir' '
249+
test_when_finished "rm -rf a/" &&
250+
251+
mkdir -p a/b/c &&
252+
>a/b/c/untracked &&
253+
>a/b/c/tracked &&
254+
git add a/b/c/tracked &&
255+
256+
(
257+
cd a/b &&
258+
git rm -rf ../b
259+
) &&
260+
261+
test_path_is_dir a/b &&
262+
test_path_is_missing a/b/c/tracked &&
263+
test_path_is_file a/b/c/untracked
264+
'
265+
266+
test_expect_success '`rm -rf dir` even with only tracked files will remove something else' '
267+
test_when_finished "rm -rf a/" &&
268+
269+
mkdir -p a/b/c &&
270+
>a/b/c/tracked &&
271+
git add a/b/c/tracked &&
272+
273+
(
274+
cd a/b &&
275+
git rm -rf ../b
276+
) &&
277+
278+
test_path_is_missing a/b/c/tracked &&
279+
## We would prefer if a/b was still present, though empty, since it
280+
## was the current working directory
281+
#test_path_is_dir a/b
282+
## But the current behavior is that it not only deletes the directory
283+
## a/b as requested, but also goes and deletes a
284+
test_path_is_missing a
285+
'
286+
287+
test_expect_success 'git version continues working from a deleted dir' '
288+
mkdir tmp &&
289+
(
290+
cd tmp &&
291+
rm -rf ../tmp &&
292+
git version
293+
)
294+
'
295+
296+
test_submodule_removal () {
297+
path_status=$1 &&
298+
shift &&
299+
300+
test_status=
301+
test "$path_status" = dir && test_status=test_must_fail
302+
303+
# Actually, while path_status=dir && test_status=test_must_fail
304+
# reflect our desired behavior, current behavior is:
305+
path_status=missing
306+
test_status=
307+
308+
test_when_finished "git reset --hard HEAD~1" &&
309+
test_when_finished "rm -rf .git/modules/my_submodule" &&
310+
311+
git checkout foo/bar/baz &&
312+
313+
git init my_submodule &&
314+
touch my_submodule/file &&
315+
git -C my_submodule add file &&
316+
git -C my_submodule commit -m "initial commit" &&
317+
git submodule add ./my_submodule &&
318+
git commit -m "Add the submodule" &&
319+
320+
(
321+
cd my_submodule &&
322+
$test_status "$@"
323+
) &&
324+
325+
test_path_is_${path_status} my_submodule
326+
}
327+
328+
test_expect_success 'rm -r with -C leaves submodule if cwd inside' '
329+
test_submodule_removal dir git -C .. rm -r my_submodule/
330+
'
331+
332+
test_expect_success 'rm -r leaves submodule if cwd inside' '
333+
test_submodule_removal dir \
334+
git --git-dir=../.git --work-tree=.. rm -r ../my_submodule/
335+
'
336+
337+
test_expect_success 'rm -rf removes submodule even if cwd inside' '
338+
test_submodule_removal missing \
339+
git --git-dir=../.git --work-tree=.. rm -rf ../my_submodule/
340+
'
341+
342+
test_done

0 commit comments

Comments
 (0)