Skip to content

Commit ae2f203

Browse files
committed
clean: preserve nested git worktree in subdirectories
remove_dir_recursively() has a check to avoid removing the directory it was asked to remove without recursing into it and report success when the directory is the top level of a working tree of a nested git repository, to protect such a repository from "clean -f" (without double -f). If a working tree of a nested git repository is in a subdirectory of a toplevel project, however, this protection did not apply by mistake; we forgot to pass the REMOVE_DIR_KEEP_NESTED_GIT down to the recursive removal codepath. This requires us to also teach the higher level not to remove the directory it is asked to remove, when the recursed invocation did not remove the directory it was asked to remove due to a nested git repository, as it is not an error to leave the parent directories of such a nested repository. Signed-off-by: Junio C Hamano <[email protected]>
1 parent c844a80 commit ae2f203

File tree

2 files changed

+43
-11
lines changed

2 files changed

+43
-11
lines changed

dir.c

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1172,23 +1172,27 @@ int is_empty_dir(const char *path)
11721172
return ret;
11731173
}
11741174

1175-
int remove_dir_recursively(struct strbuf *path, int flag)
1175+
static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)
11761176
{
11771177
DIR *dir;
11781178
struct dirent *e;
1179-
int ret = 0, original_len = path->len, len;
1179+
int ret = 0, original_len = path->len, len, kept_down = 0;
11801180
int only_empty = (flag & REMOVE_DIR_EMPTY_ONLY);
11811181
int keep_toplevel = (flag & REMOVE_DIR_KEEP_TOPLEVEL);
11821182
unsigned char submodule_head[20];
11831183

11841184
if ((flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
1185-
!resolve_gitlink_ref(path->buf, "HEAD", submodule_head))
1185+
!resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) {
11861186
/* Do not descend and nuke a nested git work tree. */
1187+
if (kept_up)
1188+
*kept_up = 1;
11871189
return 0;
1190+
}
11881191

1189-
flag &= ~(REMOVE_DIR_KEEP_TOPLEVEL|REMOVE_DIR_KEEP_NESTED_GIT);
1192+
flag &= ~REMOVE_DIR_KEEP_TOPLEVEL;
11901193
dir = opendir(path->buf);
11911194
if (!dir) {
1195+
/* an empty dir could be removed even if it is unreadble */
11921196
if (!keep_toplevel)
11931197
return rmdir(path->buf);
11941198
else
@@ -1208,7 +1212,7 @@ int remove_dir_recursively(struct strbuf *path, int flag)
12081212
if (lstat(path->buf, &st))
12091213
; /* fall thru */
12101214
else if (S_ISDIR(st.st_mode)) {
1211-
if (!remove_dir_recursively(path, flag))
1215+
if (!remove_dir_recurse(path, flag, &kept_down))
12121216
continue; /* happy */
12131217
} else if (!only_empty && !unlink(path->buf))
12141218
continue; /* happy, too */
@@ -1220,11 +1224,22 @@ int remove_dir_recursively(struct strbuf *path, int flag)
12201224
closedir(dir);
12211225

12221226
strbuf_setlen(path, original_len);
1223-
if (!ret && !keep_toplevel)
1227+
if (!ret && !keep_toplevel && !kept_down)
12241228
ret = rmdir(path->buf);
1229+
else if (kept_up)
1230+
/*
1231+
* report the uplevel that it is not an error that we
1232+
* did not rmdir() our directory.
1233+
*/
1234+
*kept_up = !ret;
12251235
return ret;
12261236
}
12271237

1238+
int remove_dir_recursively(struct strbuf *path, int flag)
1239+
{
1240+
return remove_dir_recurse(path, flag, NULL);
1241+
}
1242+
12281243
void setup_standard_excludes(struct dir_struct *dir)
12291244
{
12301245
const char *path;

t/t7300-clean.sh

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -399,8 +399,8 @@ test_expect_success SANITY 'removal failure' '
399399
'
400400

401401
test_expect_success 'nested git work tree' '
402-
rm -fr foo bar &&
403-
mkdir foo bar &&
402+
rm -fr foo bar baz &&
403+
mkdir -p foo bar baz/boo &&
404404
(
405405
cd foo &&
406406
git init &&
@@ -412,15 +412,24 @@ test_expect_success 'nested git work tree' '
412412
cd bar &&
413413
>goodbye.people
414414
) &&
415+
(
416+
cd baz/boo &&
417+
git init &&
418+
>deeper.world
419+
git add . &&
420+
git commit -a -m deeply.nested
421+
) &&
415422
git clean -f -d &&
416423
test -f foo/.git/index &&
417424
test -f foo/hello.world &&
425+
test -f baz/boo/.git/index &&
426+
test -f baz/boo/deeper.world &&
418427
! test -d bar
419428
'
420429

421430
test_expect_success 'force removal of nested git work tree' '
422-
rm -fr foo bar &&
423-
mkdir foo bar &&
431+
rm -fr foo bar baz &&
432+
mkdir -p foo bar baz/boo &&
424433
(
425434
cd foo &&
426435
git init &&
@@ -432,9 +441,17 @@ test_expect_success 'force removal of nested git work tree' '
432441
cd bar &&
433442
>goodbye.people
434443
) &&
444+
(
445+
cd baz/boo &&
446+
git init &&
447+
>deeper.world
448+
git add . &&
449+
git commit -a -m deeply.nested
450+
) &&
435451
git clean -f -f -d &&
436452
! test -d foo &&
437-
! test -d bar
453+
! test -d bar &&
454+
! test -d baz
438455
'
439456

440457
test_expect_success 'git clean -e' '

0 commit comments

Comments
 (0)