When we taught read_directory_recursive() to recurse into untracked directories in search of ignored files given DIR_SHOW_IGNORED_TOO, that had the side effect of teaching it to collect the untracked contents of untracked directories. It doesn't always make sense to return these, though (we do need them for `clean -d`), so we introduce a flag (DIR_KEEP_UNTRACKED_CONTENTS) to control whether or not read_directory() strips dir->entries of the untracked contents of untracked dirs. We also introduce check_contains() to check if one dir_entry corresponds to a path which contains the path corresponding to another dir_entry. Signed-off-by: Samuel Lijin <sxlijin@xxxxxxxxx> --- dir.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ dir.h | 3 ++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/dir.c b/dir.c index 6bd0350e9..214a148ee 100644 --- a/dir.c +++ b/dir.c @@ -1852,6 +1852,14 @@ static int cmp_name(const void *p1, const void *p2) return name_compare(e1->name, e1->len, e2->name, e2->len); } +/* check if *out lexically contains *in */ +static int check_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, const char *path, int len, const struct pathspec *pathspec) @@ -2067,6 +2075,52 @@ int read_directory(struct dir_struct *dir, const char *path, read_directory_recursive(dir, path, len, untracked, 0, pathspec); QSORT(dir->entries, dir->nr, cmp_name); QSORT(dir->ignored, dir->ignored_nr, cmp_name); + + // if DIR_SHOW_IGNORED_TOO, 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, nr_removed = 0; + + // remove from dir->entries untracked contents of untracked dirs + for (i = 0; i < dir->nr; i++) { + if (!dir->entries[i]) + continue; + + for (j = i + 1; j < dir->nr; j++) { + if (!dir->entries[j]) + continue; + if (check_contains(dir->entries[i], dir->entries[j])) { + nr_removed++; + free(dir->entries[j]); + dir->entries[j] = NULL; + } + else { + break; + } + } + } + + // strip dir->entries of NULLs + if (nr_removed) { + for (i = 0;;) { + while (i < dir->nr && dir->entries[i]) + i++; + if (i == dir->nr) + break; + j = i; + while (j < dir->nr && !dir->entries[j]) + j++; + if (j == dir->nr) + break; + dir->entries[i] = dir->entries[j]; + dir->entries[j] = NULL; + } + dir->nr -= nr_removed; + } + } + if (dir->untracked) { static struct trace_key trace_untracked_stats = TRACE_KEY_INIT(UNTRACKED_STATS); trace_printf_key(&trace_untracked_stats, diff --git a/dir.h b/dir.h index bf23a470a..650e54bdf 100644 --- a/dir.h +++ b/dir.h @@ -151,7 +151,8 @@ struct dir_struct { DIR_NO_GITLINKS = 1<<3, DIR_COLLECT_IGNORED = 1<<4, 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; struct dir_entry **entries; struct dir_entry **ignored; -- 2.12.2