Merge branch 'sl/clean-d-ignored-fix' into maint
"git clean -d" used to clean directories that has ignored files, even though the command should not lose ignored ones without "-x". "git status --ignored" did not list ignored and untracked files without "-uall". These have been corrected. * sl/clean-d-ignored-fix: clean: teach clean -d to preserve ignored paths dir: expose cmp_name() and check_contains() dir: hide untracked contents of untracked dirs dir: recurse into untracked dirs for ignored files t7061: status --ignored should search untracked dirs t7300: clean -d should skip dirs with ignored files
This commit is contained in:
@ -33,6 +33,12 @@ The notable options are:
|
|||||||
Similar to `DIR_SHOW_IGNORED`, but return ignored files in `ignored[]`
|
Similar to `DIR_SHOW_IGNORED`, but return ignored files in `ignored[]`
|
||||||
in addition to untracked files in `entries[]`.
|
in addition to untracked files in `entries[]`.
|
||||||
|
|
||||||
|
`DIR_KEEP_UNTRACKED_CONTENTS`:::
|
||||||
|
|
||||||
|
Only has meaning if `DIR_SHOW_IGNORED_TOO` is also set; if this is set, the
|
||||||
|
untracked contents of untracked directories are also returned in
|
||||||
|
`entries[]`.
|
||||||
|
|
||||||
`DIR_COLLECT_IGNORED`:::
|
`DIR_COLLECT_IGNORED`:::
|
||||||
|
|
||||||
Special mode for git-add. Return ignored files in `ignored[]` and
|
Special mode for git-add. Return ignored files in `ignored[]` and
|
||||||
|
@ -857,6 +857,38 @@ static void interactive_main_loop(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void correct_untracked_entries(struct dir_struct *dir)
|
||||||
|
{
|
||||||
|
int src, dst, ign;
|
||||||
|
|
||||||
|
for (src = dst = ign = 0; src < dir->nr; src++) {
|
||||||
|
/* skip paths in ignored[] that cannot be inside entries[src] */
|
||||||
|
while (ign < dir->ignored_nr &&
|
||||||
|
0 <= cmp_dir_entry(&dir->entries[src], &dir->ignored[ign]))
|
||||||
|
ign++;
|
||||||
|
|
||||||
|
if (ign < dir->ignored_nr &&
|
||||||
|
check_dir_entry_contains(dir->entries[src], dir->ignored[ign])) {
|
||||||
|
/* entries[src] contains an ignored path, so we drop it */
|
||||||
|
free(dir->entries[src]);
|
||||||
|
} else {
|
||||||
|
struct dir_entry *ent = dir->entries[src++];
|
||||||
|
|
||||||
|
/* entries[src] does not contain an ignored path, so we keep it */
|
||||||
|
dir->entries[dst++] = ent;
|
||||||
|
|
||||||
|
/* then discard paths in entries[] contained inside entries[src] */
|
||||||
|
while (src < dir->nr &&
|
||||||
|
check_dir_entry_contains(ent, dir->entries[src]))
|
||||||
|
free(dir->entries[src++]);
|
||||||
|
|
||||||
|
/* compensate for the outer loop's loop control */
|
||||||
|
src--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dir->nr = dst;
|
||||||
|
}
|
||||||
|
|
||||||
int cmd_clean(int argc, const char **argv, const char *prefix)
|
int cmd_clean(int argc, const char **argv, const char *prefix)
|
||||||
{
|
{
|
||||||
int i, res;
|
int i, res;
|
||||||
@ -916,6 +948,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
|
|||||||
|
|
||||||
dir.flags |= DIR_SHOW_OTHER_DIRECTORIES;
|
dir.flags |= DIR_SHOW_OTHER_DIRECTORIES;
|
||||||
|
|
||||||
|
if (remove_directories)
|
||||||
|
dir.flags |= DIR_SHOW_IGNORED_TOO | DIR_KEEP_UNTRACKED_CONTENTS;
|
||||||
|
|
||||||
if (read_cache() < 0)
|
if (read_cache() < 0)
|
||||||
die(_("index file corrupt"));
|
die(_("index file corrupt"));
|
||||||
|
|
||||||
@ -931,6 +966,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
|
|||||||
prefix, argv);
|
prefix, argv);
|
||||||
|
|
||||||
fill_directory(&dir, &pathspec);
|
fill_directory(&dir, &pathspec);
|
||||||
|
correct_untracked_entries(&dir);
|
||||||
|
|
||||||
for (i = 0; i < dir.nr; i++) {
|
for (i = 0; i < dir.nr; i++) {
|
||||||
struct dir_entry *ent = dir.entries[i];
|
struct dir_entry *ent = dir.entries[i];
|
||||||
@ -958,6 +994,12 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
|
|||||||
string_list_append(&del_list, rel);
|
string_list_append(&del_list, rel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < dir.nr; i++)
|
||||||
|
free(dir.entries[i]);
|
||||||
|
|
||||||
|
for (i = 0; i < dir.ignored_nr; i++)
|
||||||
|
free(dir.ignored[i]);
|
||||||
|
|
||||||
if (interactive && del_list.nr > 0)
|
if (interactive && del_list.nr > 0)
|
||||||
interactive_main_loop();
|
interactive_main_loop();
|
||||||
|
|
||||||
|
43
dir.c
43
dir.c
@ -1784,7 +1784,10 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
|
|||||||
dir_state = state;
|
dir_state = state;
|
||||||
|
|
||||||
/* recurse into subdir if instructed by treat_path */
|
/* recurse into subdir if instructed by treat_path */
|
||||||
if (state == path_recurse) {
|
if ((state == path_recurse) ||
|
||||||
|
((state == path_untracked) &&
|
||||||
|
(dir->flags & DIR_SHOW_IGNORED_TOO) &&
|
||||||
|
(get_dtype(cdir.de, path.buf, path.len) == DT_DIR))) {
|
||||||
struct untracked_cache_dir *ud;
|
struct untracked_cache_dir *ud;
|
||||||
ud = lookup_untracked(dir->untracked, untracked,
|
ud = lookup_untracked(dir->untracked, untracked,
|
||||||
path.buf + baselen,
|
path.buf + baselen,
|
||||||
@ -1839,7 +1842,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
|
|||||||
return dir_state;
|
return dir_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int cmp_name(const void *p1, const void *p2)
|
int cmp_dir_entry(const void *p1, const void *p2)
|
||||||
{
|
{
|
||||||
const struct dir_entry *e1 = *(const struct dir_entry **)p1;
|
const struct dir_entry *e1 = *(const struct dir_entry **)p1;
|
||||||
const struct dir_entry *e2 = *(const struct dir_entry **)p2;
|
const struct dir_entry *e2 = *(const struct dir_entry **)p2;
|
||||||
@ -1847,6 +1850,14 @@ static int cmp_name(const void *p1, const void *p2)
|
|||||||
return name_compare(e1->name, e1->len, e2->name, e2->len);
|
return name_compare(e1->name, e1->len, e2->name, e2->len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* check if *out lexically strictly contains *in */
|
||||||
|
int check_dir_entry_contains(const struct dir_entry *out, const struct dir_entry *in)
|
||||||
|
{
|
||||||
|
return (out->len < in->len) &&
|
||||||
|
(out->name[out->len - 1] == '/') &&
|
||||||
|
!memcmp(out->name, in->name, out->len);
|
||||||
|
}
|
||||||
|
|
||||||
static int treat_leading_path(struct dir_struct *dir,
|
static int treat_leading_path(struct dir_struct *dir,
|
||||||
const char *path, int len,
|
const char *path, int len,
|
||||||
const struct pathspec *pathspec)
|
const struct pathspec *pathspec)
|
||||||
@ -2060,8 +2071,32 @@ int read_directory(struct dir_struct *dir, const char *path,
|
|||||||
dir->untracked = NULL;
|
dir->untracked = NULL;
|
||||||
if (!len || treat_leading_path(dir, path, len, pathspec))
|
if (!len || treat_leading_path(dir, path, len, pathspec))
|
||||||
read_directory_recursive(dir, path, len, untracked, 0, pathspec);
|
read_directory_recursive(dir, path, len, untracked, 0, pathspec);
|
||||||
QSORT(dir->entries, dir->nr, cmp_name);
|
QSORT(dir->entries, dir->nr, cmp_dir_entry);
|
||||||
QSORT(dir->ignored, dir->ignored_nr, cmp_name);
|
QSORT(dir->ignored, dir->ignored_nr, cmp_dir_entry);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If DIR_SHOW_IGNORED_TOO is set, read_directory_recursive() will
|
||||||
|
* also pick up untracked contents of untracked dirs; by default
|
||||||
|
* we discard these, but given DIR_KEEP_UNTRACKED_CONTENTS we do not.
|
||||||
|
*/
|
||||||
|
if ((dir->flags & DIR_SHOW_IGNORED_TOO) &&
|
||||||
|
!(dir->flags & DIR_KEEP_UNTRACKED_CONTENTS)) {
|
||||||
|
int i, j;
|
||||||
|
|
||||||
|
/* remove from dir->entries untracked contents of untracked dirs */
|
||||||
|
for (i = j = 0; j < dir->nr; j++) {
|
||||||
|
if (i &&
|
||||||
|
check_dir_entry_contains(dir->entries[i - 1], dir->entries[j])) {
|
||||||
|
free(dir->entries[j]);
|
||||||
|
dir->entries[j] = NULL;
|
||||||
|
} else {
|
||||||
|
dir->entries[i++] = dir->entries[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dir->nr = i;
|
||||||
|
}
|
||||||
|
|
||||||
if (dir->untracked) {
|
if (dir->untracked) {
|
||||||
static struct trace_key trace_untracked_stats = TRACE_KEY_INIT(UNTRACKED_STATS);
|
static struct trace_key trace_untracked_stats = TRACE_KEY_INIT(UNTRACKED_STATS);
|
||||||
trace_printf_key(&trace_untracked_stats,
|
trace_printf_key(&trace_untracked_stats,
|
||||||
|
6
dir.h
6
dir.h
@ -151,7 +151,8 @@ struct dir_struct {
|
|||||||
DIR_NO_GITLINKS = 1<<3,
|
DIR_NO_GITLINKS = 1<<3,
|
||||||
DIR_COLLECT_IGNORED = 1<<4,
|
DIR_COLLECT_IGNORED = 1<<4,
|
||||||
DIR_SHOW_IGNORED_TOO = 1<<5,
|
DIR_SHOW_IGNORED_TOO = 1<<5,
|
||||||
DIR_COLLECT_KILLED_ONLY = 1<<6
|
DIR_COLLECT_KILLED_ONLY = 1<<6,
|
||||||
|
DIR_KEEP_UNTRACKED_CONTENTS = 1<<7
|
||||||
} flags;
|
} flags;
|
||||||
struct dir_entry **entries;
|
struct dir_entry **entries;
|
||||||
struct dir_entry **ignored;
|
struct dir_entry **ignored;
|
||||||
@ -326,6 +327,9 @@ static inline int dir_path_match(const struct dir_entry *ent,
|
|||||||
has_trailing_dir);
|
has_trailing_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int cmp_dir_entry(const void *p1, const void *p2);
|
||||||
|
int check_dir_entry_contains(const struct dir_entry *out, const struct dir_entry *in);
|
||||||
|
|
||||||
void untracked_cache_invalidate_path(struct index_state *, const char *);
|
void untracked_cache_invalidate_path(struct index_state *, const char *);
|
||||||
void untracked_cache_remove_from_index(struct index_state *, const char *);
|
void untracked_cache_remove_from_index(struct index_state *, const char *);
|
||||||
void untracked_cache_add_to_index(struct index_state *, const char *);
|
void untracked_cache_add_to_index(struct index_state *, const char *);
|
||||||
|
@ -9,6 +9,7 @@ cat >expected <<\EOF
|
|||||||
?? actual
|
?? actual
|
||||||
?? expected
|
?? expected
|
||||||
?? untracked/
|
?? untracked/
|
||||||
|
!! untracked/ignored
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
test_expect_success 'status untracked directory with --ignored' '
|
test_expect_success 'status untracked directory with --ignored' '
|
||||||
|
@ -653,4 +653,20 @@ test_expect_success 'git clean -d respects pathspecs (pathspec is prefix of dir)
|
|||||||
test_path_is_dir foobar
|
test_path_is_dir foobar
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git clean -d skips untracked dirs containing ignored files' '
|
||||||
|
echo /foo/bar >.gitignore &&
|
||||||
|
echo ignoreme >>.gitignore &&
|
||||||
|
rm -rf foo &&
|
||||||
|
mkdir -p foo/a/aa/aaa foo/b/bb/bbb &&
|
||||||
|
touch foo/bar foo/baz foo/a/aa/ignoreme foo/b/ignoreme foo/b/bb/1 foo/b/bb/2 &&
|
||||||
|
git clean -df &&
|
||||||
|
test_path_is_dir foo &&
|
||||||
|
test_path_is_file foo/bar &&
|
||||||
|
test_path_is_missing foo/baz &&
|
||||||
|
test_path_is_file foo/a/aa/ignoreme &&
|
||||||
|
test_path_is_missing foo/a/aa/aaa &&
|
||||||
|
test_path_is_file foo/b/ignoreme &&
|
||||||
|
test_path_is_missing foo/b/bb
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
Reference in New Issue
Block a user