@matheusbsbs / matheus-bagliano / commits / a0f4afbe87
Fork 0

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>

Junio C Hamano committed Jun 30, 2009 at 15:33 UTC a0f4afbe87ddda7902e36350d163dea146166550
6 files changed +63 -5
Documentation/git-clean.txt
+3
@@ -27,6 +27,9 @@ OPTIONS
27 -------
28 -d::
29 Remove untracked directories in addition to untracked files.
30 + If an untracked directory is managed by a different git
31 + repository, it is not removed by default. Use -f option twice
32 + if you really want to remove such a directory.
33
34 -f::
35 If the git configuration specifies clean.requireForce as true,
builtin-clean.c
+6 -1
@@ -31,6 +31,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
31 int i;
32 int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0;
33 int ignored_only = 0, baselen = 0, config_set = 0, errors = 0;
34 + int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT;
35 struct strbuf directory = STRBUF_INIT;
36 struct dir_struct dir;
37 static const char **pathspec;
@@ -69,6 +70,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
70 die("clean.requireForce%s set and -n or -f not given; "
71 "refusing to clean", config_set ? "" : " not");
72
73 + if (force > 1)
74 + rm_flags = 0;
75 +
76 dir.flags |= DIR_SHOW_OTHER_DIRECTORIES;
77
78 if (!ignored)
@@ -131,7 +135,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
135 (matches == MATCHED_EXACTLY)) {
136 if (!quiet)
137 printf("Removing %s\n", qname);
134 - if (remove_dir_recursively(&directory, 0) != 0) {
138 + if (remove_dir_recursively(&directory,
139 + rm_flags) != 0) {
140 warning("failed to remove '%s'", qname);
141 errors++;
142 }
dir.c
+10 -2
@@ -861,12 +861,20 @@ int is_empty_dir(const char *path)
861 return ret;
862 }
863
864 -int remove_dir_recursively(struct strbuf *path, int only_empty)
864 +int remove_dir_recursively(struct strbuf *path, int flag)
865 {
866 - DIR *dir = opendir(path->buf);
866 + DIR *dir;
867 struct dirent *e;
868 int ret = 0, original_len = path->len, len;
869 + int only_empty = (flag & REMOVE_DIR_EMPTY_ONLY);
870 + unsigned char submodule_head[20];
871
872 + if ((flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
873 + !resolve_gitlink_ref(path->buf, "HEAD", submodule_head))
874 + /* Do not descend and nuke a nested git work tree. */
875 + return 0;
876 +
877 + dir = opendir(path->buf);
878 if (!dir)
879 return -1;
880 if (path->buf[original_len - 1] != '/')
dir.h
+4 -1
@@ -88,7 +88,10 @@ static inline int is_dot_or_dotdot(const char *name)
88 extern int is_empty_dir(const char *dir);
89
90 extern void setup_standard_excludes(struct dir_struct *dir);
91 -extern int remove_dir_recursively(struct strbuf *path, int only_empty);
91 +
92 +#define REMOVE_DIR_EMPTY_ONLY 01
93 +#define REMOVE_DIR_KEEP_NESTED_GIT 02
94 +extern int remove_dir_recursively(struct strbuf *path, int flag);
95
96 /* tries to remove the path with empty directories along it, ignores ENOENT */
97 extern int remove_path(const char *path);
refs.c
+1 -1
@@ -821,7 +821,7 @@ static int remove_empty_directories(const char *file)
821 strbuf_init(&path, 20);
822 strbuf_addstr(&path, file);
823
824 - result = remove_dir_recursively(&path, 1);
824 + result = remove_dir_recursively(&path, REMOVE_DIR_EMPTY_ONLY);
825
826 strbuf_release(&path);
827
t/t7300-clean.sh
+39
@@ -380,4 +380,43 @@ test_expect_success 'removal failure' '
380 '
381 chmod 755 foo
382
383 +test_expect_success 'nested git work tree' '
384 + rm -fr foo bar &&
385 + mkdir foo bar &&
386 + (
387 + cd foo &&
388 + git init &&
389 + >hello.world
390 + git add . &&
391 + git commit -a -m nested
392 + ) &&
393 + (
394 + cd bar &&
395 + >goodbye.people
396 + ) &&
397 + git clean -f -d &&
398 + test -f foo/.git/index &&
399 + test -f foo/hello.world &&
400 + ! test -d bar
401 +'
402 +
403 +test_expect_success 'force removal of nested git work tree' '
404 + rm -fr foo bar &&
405 + mkdir foo bar &&
406 + (
407 + cd foo &&
408 + git init &&
409 + >hello.world
410 + git add . &&
411 + git commit -a -m nested
412 + ) &&
413 + (
414 + cd bar &&
415 + >goodbye.people
416 + ) &&
417 + git clean -f -f -d &&
418 + ! test -d foo &&
419 + ! test -d bar
420 +'
421 +
422 test_done