dir: hide untracked contents of untracked dirs
authorSamuel Lijin <sxlijin@gmail.com>
Thu, 18 May 2017 08:21:52 +0000 (04:21 -0400)
committerJunio C Hamano <gitster@pobox.com>
Mon, 22 May 2017 03:14:09 +0000 (12:14 +0900)
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.

This also fixes known breakages in t7061, since status --ignored now
searches untracked directories for ignored files.

Signed-off-by: Samuel Lijin <sxlijin@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/technical/api-directory-listing.txt
dir.c
dir.h
t/t7061-wtstatus-ignore.sh
index 7f8e78d916c0b38ed6757aee6578a53cab30d9d0..6c77b4920c92a0b327fb88d2b461ec0d918ec99a 100644 (file)
@@ -33,6 +33,12 @@ The notable options are:
        Similar to `DIR_SHOW_IGNORED`, but return ignored files in `ignored[]`
        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`:::
 
        Special mode for git-add. Return ignored files in `ignored[]` and
diff --git a/dir.c b/dir.c
index 6b4eeef584309fb31fdf2fac78e220f990344b9a..61561ea91d8ede03416607469cefa9abccf12f85 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -1813,6 +1813,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 strictly 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)
@@ -2028,6 +2036,29 @@ 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 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_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) {
                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 bf23a470af57775c8f0099597f7a403995bf3a89..650e54bdf64e3fbbd3be1a0a5d3728e5a21298d1 100644 (file)
--- 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;
index 15e7592b6b07b00176d0672ade2d2e05b12abdf9..fc6013ba3c83aa7243f15595bc7b6ddd8b8f46dc 100755 (executable)
@@ -12,7 +12,7 @@ cat >expected <<\EOF
 !! untracked/ignored
 EOF
 
-test_expect_failure 'status untracked directory with --ignored' '
+test_expect_success 'status untracked directory with --ignored' '
        echo "ignored" >.gitignore &&
        mkdir untracked &&
        : >untracked/ignored &&
@@ -21,7 +21,7 @@ test_expect_failure 'status untracked directory with --ignored' '
        test_cmp expected actual
 '
 
-test_expect_failure 'same with gitignore starting with BOM' '
+test_expect_success 'same with gitignore starting with BOM' '
        printf "\357\273\277ignored\n" >.gitignore &&
        mkdir -p untracked &&
        : >untracked/ignored &&