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 <sxli...@gmail.com>
---
 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

Reply via email to