clean: require double -f options to nuke nested git repository and work tree
When you have an embedded git work tree in your work tree (be it an orphaned submodule, or an independent checkout of an unrelated project), "git clean -d -f" blindly descended into it and removed everything. This is rarely what the user wants. Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
0a53e9ddea
commit
a0f4afbe87
@ -27,6 +27,9 @@ OPTIONS
|
|||||||
-------
|
-------
|
||||||
-d::
|
-d::
|
||||||
Remove untracked directories in addition to untracked files.
|
Remove untracked directories in addition to untracked files.
|
||||||
|
If an untracked directory is managed by a different git
|
||||||
|
repository, it is not removed by default. Use -f option twice
|
||||||
|
if you really want to remove such a directory.
|
||||||
|
|
||||||
-f::
|
-f::
|
||||||
If the git configuration specifies clean.requireForce as true,
|
If the git configuration specifies clean.requireForce as true,
|
||||||
|
@ -31,6 +31,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
|
|||||||
int i;
|
int i;
|
||||||
int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0;
|
int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0;
|
||||||
int ignored_only = 0, baselen = 0, config_set = 0, errors = 0;
|
int ignored_only = 0, baselen = 0, config_set = 0, errors = 0;
|
||||||
|
int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT;
|
||||||
struct strbuf directory = STRBUF_INIT;
|
struct strbuf directory = STRBUF_INIT;
|
||||||
struct dir_struct dir;
|
struct dir_struct dir;
|
||||||
static const char **pathspec;
|
static const char **pathspec;
|
||||||
@ -69,6 +70,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
|
|||||||
die("clean.requireForce%s set and -n or -f not given; "
|
die("clean.requireForce%s set and -n or -f not given; "
|
||||||
"refusing to clean", config_set ? "" : " not");
|
"refusing to clean", config_set ? "" : " not");
|
||||||
|
|
||||||
|
if (force > 1)
|
||||||
|
rm_flags = 0;
|
||||||
|
|
||||||
dir.flags |= DIR_SHOW_OTHER_DIRECTORIES;
|
dir.flags |= DIR_SHOW_OTHER_DIRECTORIES;
|
||||||
|
|
||||||
if (!ignored)
|
if (!ignored)
|
||||||
@ -131,7 +135,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
|
|||||||
(matches == MATCHED_EXACTLY)) {
|
(matches == MATCHED_EXACTLY)) {
|
||||||
if (!quiet)
|
if (!quiet)
|
||||||
printf("Removing %s\n", qname);
|
printf("Removing %s\n", qname);
|
||||||
if (remove_dir_recursively(&directory, 0) != 0) {
|
if (remove_dir_recursively(&directory,
|
||||||
|
rm_flags) != 0) {
|
||||||
warning("failed to remove '%s'", qname);
|
warning("failed to remove '%s'", qname);
|
||||||
errors++;
|
errors++;
|
||||||
}
|
}
|
||||||
|
12
dir.c
12
dir.c
@ -861,12 +861,20 @@ int is_empty_dir(const char *path)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int remove_dir_recursively(struct strbuf *path, int only_empty)
|
int remove_dir_recursively(struct strbuf *path, int flag)
|
||||||
{
|
{
|
||||||
DIR *dir = opendir(path->buf);
|
DIR *dir;
|
||||||
struct dirent *e;
|
struct dirent *e;
|
||||||
int ret = 0, original_len = path->len, len;
|
int ret = 0, original_len = path->len, len;
|
||||||
|
int only_empty = (flag & REMOVE_DIR_EMPTY_ONLY);
|
||||||
|
unsigned char submodule_head[20];
|
||||||
|
|
||||||
|
if ((flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
|
||||||
|
!resolve_gitlink_ref(path->buf, "HEAD", submodule_head))
|
||||||
|
/* Do not descend and nuke a nested git work tree. */
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
dir = opendir(path->buf);
|
||||||
if (!dir)
|
if (!dir)
|
||||||
return -1;
|
return -1;
|
||||||
if (path->buf[original_len - 1] != '/')
|
if (path->buf[original_len - 1] != '/')
|
||||||
|
5
dir.h
5
dir.h
@ -88,7 +88,10 @@ static inline int is_dot_or_dotdot(const char *name)
|
|||||||
extern int is_empty_dir(const char *dir);
|
extern int is_empty_dir(const char *dir);
|
||||||
|
|
||||||
extern void setup_standard_excludes(struct dir_struct *dir);
|
extern void setup_standard_excludes(struct dir_struct *dir);
|
||||||
extern int remove_dir_recursively(struct strbuf *path, int only_empty);
|
|
||||||
|
#define REMOVE_DIR_EMPTY_ONLY 01
|
||||||
|
#define REMOVE_DIR_KEEP_NESTED_GIT 02
|
||||||
|
extern int remove_dir_recursively(struct strbuf *path, int flag);
|
||||||
|
|
||||||
/* tries to remove the path with empty directories along it, ignores ENOENT */
|
/* tries to remove the path with empty directories along it, ignores ENOENT */
|
||||||
extern int remove_path(const char *path);
|
extern int remove_path(const char *path);
|
||||||
|
2
refs.c
2
refs.c
@ -821,7 +821,7 @@ static int remove_empty_directories(const char *file)
|
|||||||
strbuf_init(&path, 20);
|
strbuf_init(&path, 20);
|
||||||
strbuf_addstr(&path, file);
|
strbuf_addstr(&path, file);
|
||||||
|
|
||||||
result = remove_dir_recursively(&path, 1);
|
result = remove_dir_recursively(&path, REMOVE_DIR_EMPTY_ONLY);
|
||||||
|
|
||||||
strbuf_release(&path);
|
strbuf_release(&path);
|
||||||
|
|
||||||
|
@ -380,4 +380,43 @@ test_expect_success 'removal failure' '
|
|||||||
'
|
'
|
||||||
chmod 755 foo
|
chmod 755 foo
|
||||||
|
|
||||||
|
test_expect_success 'nested git work tree' '
|
||||||
|
rm -fr foo bar &&
|
||||||
|
mkdir foo bar &&
|
||||||
|
(
|
||||||
|
cd foo &&
|
||||||
|
git init &&
|
||||||
|
>hello.world
|
||||||
|
git add . &&
|
||||||
|
git commit -a -m nested
|
||||||
|
) &&
|
||||||
|
(
|
||||||
|
cd bar &&
|
||||||
|
>goodbye.people
|
||||||
|
) &&
|
||||||
|
git clean -f -d &&
|
||||||
|
test -f foo/.git/index &&
|
||||||
|
test -f foo/hello.world &&
|
||||||
|
! test -d bar
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'force removal of nested git work tree' '
|
||||||
|
rm -fr foo bar &&
|
||||||
|
mkdir foo bar &&
|
||||||
|
(
|
||||||
|
cd foo &&
|
||||||
|
git init &&
|
||||||
|
>hello.world
|
||||||
|
git add . &&
|
||||||
|
git commit -a -m nested
|
||||||
|
) &&
|
||||||
|
(
|
||||||
|
cd bar &&
|
||||||
|
>goodbye.people
|
||||||
|
) &&
|
||||||
|
git clean -f -f -d &&
|
||||||
|
! test -d foo &&
|
||||||
|
! test -d bar
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
Loading…
Reference in New Issue
Block a user