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 <gitster@pobox.com>
This commit is contained in:
parent
c844a80356
commit
ae2f203ef7
27
dir.c
27
dir.c
@ -1172,23 +1172,27 @@ int is_empty_dir(const char *path)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int remove_dir_recursively(struct strbuf *path, int flag)
|
static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)
|
||||||
{
|
{
|
||||||
DIR *dir;
|
DIR *dir;
|
||||||
struct dirent *e;
|
struct dirent *e;
|
||||||
int ret = 0, original_len = path->len, len;
|
int ret = 0, original_len = path->len, len, kept_down = 0;
|
||||||
int only_empty = (flag & REMOVE_DIR_EMPTY_ONLY);
|
int only_empty = (flag & REMOVE_DIR_EMPTY_ONLY);
|
||||||
int keep_toplevel = (flag & REMOVE_DIR_KEEP_TOPLEVEL);
|
int keep_toplevel = (flag & REMOVE_DIR_KEEP_TOPLEVEL);
|
||||||
unsigned char submodule_head[20];
|
unsigned char submodule_head[20];
|
||||||
|
|
||||||
if ((flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
|
if ((flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
|
||||||
!resolve_gitlink_ref(path->buf, "HEAD", submodule_head))
|
!resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) {
|
||||||
/* Do not descend and nuke a nested git work tree. */
|
/* Do not descend and nuke a nested git work tree. */
|
||||||
|
if (kept_up)
|
||||||
|
*kept_up = 1;
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
flag &= ~(REMOVE_DIR_KEEP_TOPLEVEL|REMOVE_DIR_KEEP_NESTED_GIT);
|
flag &= ~REMOVE_DIR_KEEP_TOPLEVEL;
|
||||||
dir = opendir(path->buf);
|
dir = opendir(path->buf);
|
||||||
if (!dir) {
|
if (!dir) {
|
||||||
|
/* an empty dir could be removed even if it is unreadble */
|
||||||
if (!keep_toplevel)
|
if (!keep_toplevel)
|
||||||
return rmdir(path->buf);
|
return rmdir(path->buf);
|
||||||
else
|
else
|
||||||
@ -1208,7 +1212,7 @@ int remove_dir_recursively(struct strbuf *path, int flag)
|
|||||||
if (lstat(path->buf, &st))
|
if (lstat(path->buf, &st))
|
||||||
; /* fall thru */
|
; /* fall thru */
|
||||||
else if (S_ISDIR(st.st_mode)) {
|
else if (S_ISDIR(st.st_mode)) {
|
||||||
if (!remove_dir_recursively(path, flag))
|
if (!remove_dir_recurse(path, flag, &kept_down))
|
||||||
continue; /* happy */
|
continue; /* happy */
|
||||||
} else if (!only_empty && !unlink(path->buf))
|
} else if (!only_empty && !unlink(path->buf))
|
||||||
continue; /* happy, too */
|
continue; /* happy, too */
|
||||||
@ -1220,11 +1224,22 @@ int remove_dir_recursively(struct strbuf *path, int flag)
|
|||||||
closedir(dir);
|
closedir(dir);
|
||||||
|
|
||||||
strbuf_setlen(path, original_len);
|
strbuf_setlen(path, original_len);
|
||||||
if (!ret && !keep_toplevel)
|
if (!ret && !keep_toplevel && !kept_down)
|
||||||
ret = rmdir(path->buf);
|
ret = rmdir(path->buf);
|
||||||
|
else if (kept_up)
|
||||||
|
/*
|
||||||
|
* report the uplevel that it is not an error that we
|
||||||
|
* did not rmdir() our directory.
|
||||||
|
*/
|
||||||
|
*kept_up = !ret;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int remove_dir_recursively(struct strbuf *path, int flag)
|
||||||
|
{
|
||||||
|
return remove_dir_recurse(path, flag, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
void setup_standard_excludes(struct dir_struct *dir)
|
void setup_standard_excludes(struct dir_struct *dir)
|
||||||
{
|
{
|
||||||
const char *path;
|
const char *path;
|
||||||
|
@ -399,8 +399,8 @@ test_expect_success SANITY 'removal failure' '
|
|||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'nested git work tree' '
|
test_expect_success 'nested git work tree' '
|
||||||
rm -fr foo bar &&
|
rm -fr foo bar baz &&
|
||||||
mkdir foo bar &&
|
mkdir -p foo bar baz/boo &&
|
||||||
(
|
(
|
||||||
cd foo &&
|
cd foo &&
|
||||||
git init &&
|
git init &&
|
||||||
@ -412,15 +412,24 @@ test_expect_success 'nested git work tree' '
|
|||||||
cd bar &&
|
cd bar &&
|
||||||
>goodbye.people
|
>goodbye.people
|
||||||
) &&
|
) &&
|
||||||
|
(
|
||||||
|
cd baz/boo &&
|
||||||
|
git init &&
|
||||||
|
>deeper.world
|
||||||
|
git add . &&
|
||||||
|
git commit -a -m deeply.nested
|
||||||
|
) &&
|
||||||
git clean -f -d &&
|
git clean -f -d &&
|
||||||
test -f foo/.git/index &&
|
test -f foo/.git/index &&
|
||||||
test -f foo/hello.world &&
|
test -f foo/hello.world &&
|
||||||
|
test -f baz/boo/.git/index &&
|
||||||
|
test -f baz/boo/deeper.world &&
|
||||||
! test -d bar
|
! test -d bar
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'force removal of nested git work tree' '
|
test_expect_success 'force removal of nested git work tree' '
|
||||||
rm -fr foo bar &&
|
rm -fr foo bar baz &&
|
||||||
mkdir foo bar &&
|
mkdir -p foo bar baz/boo &&
|
||||||
(
|
(
|
||||||
cd foo &&
|
cd foo &&
|
||||||
git init &&
|
git init &&
|
||||||
@ -432,9 +441,17 @@ test_expect_success 'force removal of nested git work tree' '
|
|||||||
cd bar &&
|
cd bar &&
|
||||||
>goodbye.people
|
>goodbye.people
|
||||||
) &&
|
) &&
|
||||||
|
(
|
||||||
|
cd baz/boo &&
|
||||||
|
git init &&
|
||||||
|
>deeper.world
|
||||||
|
git add . &&
|
||||||
|
git commit -a -m deeply.nested
|
||||||
|
) &&
|
||||||
git clean -f -f -d &&
|
git clean -f -f -d &&
|
||||||
! test -d foo &&
|
! test -d foo &&
|
||||||
! test -d bar
|
! test -d bar &&
|
||||||
|
! test -d baz
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'git clean -e' '
|
test_expect_success 'git clean -e' '
|
||||||
|
Loading…
Reference in New Issue
Block a user