Merge branch 'kb/status-ignored-optim-2'
authorJunio C Hamano <gitster@pobox.com>
Tue, 23 Apr 2013 18:21:23 +0000 (11:21 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 23 Apr 2013 18:21:23 +0000 (11:21 -0700)
Fixes a handful of issues in the code to traverse working tree to
find untracked and/or ignored files, cleans up and optimizes the
codepath in general.

* kb/status-ignored-optim-2:
dir.c: git-status --ignored: don't scan the work tree twice
dir.c: git-status --ignored: don't scan the work tree three times
dir.c: git-status: avoid is_excluded checks for tracked files
dir.c: replace is_path_excluded with now equivalent is_excluded API
dir.c: unify is_excluded and is_path_excluded APIs
dir.c: move prep_exclude
dir.c: factor out parts of last_exclude_matching for later reuse
dir.c: git-clean -d -X: don't delete tracked directories
dir.c: make 'git-status --ignored' work within leading directories
dir.c: git-status --ignored: don't list empty directories as ignored
dir.c: git-ls-files --directories: don't hide empty directories
dir.c: git-status --ignored: don't list empty ignored directories
dir.c: git-status --ignored: don't list files in ignored directories
dir.c: git-status --ignored: don't drop ignored directories

12 files changed:
Documentation/technical/api-directory-listing.txt
builtin/add.c
builtin/check-ignore.c
builtin/ls-files.c
dir.c
dir.h
t/t3001-ls-files-others-exclude.sh
t/t7061-wtstatus-ignore.sh
t/t7300-clean.sh
unpack-trees.c
unpack-trees.h
wt-status.c
index 1f349b28ae175327706fa67a3e0b09f1a26a9c02..7f8e78d916c0b38ed6757aee6578a53cab30d9d0 100644 (file)
@@ -22,12 +22,23 @@ The notable options are:
 
 `flags`::
 
-       A bit-field of options:
+       A bit-field of options (the `*IGNORED*` flags are mutually exclusive):
 
 `DIR_SHOW_IGNORED`:::
 
-       The traversal is for finding just ignored files, not unignored
-       files.
+       Return just ignored files in `entries[]`, not untracked files.
+
+`DIR_SHOW_IGNORED_TOO`:::
+
+       Similar to `DIR_SHOW_IGNORED`, but return ignored files in `ignored[]`
+       in addition to untracked files in `entries[]`.
+
+`DIR_COLLECT_IGNORED`:::
+
+       Special mode for git-add. Return ignored files in `ignored[]` and
+       untracked files in `entries[]`. Only returns ignored files that match
+       pathspec exactly (no wildcards). Does not recurse into ignored
+       directories.
 
 `DIR_SHOW_OTHER_DIRECTORIES`:::
 
@@ -57,6 +68,14 @@ The result of the enumeration is left in these fields:
 
        Internal use; keeps track of allocation of `entries[]` array.
 
+`ignored[]`::
+
+       An array of `struct dir_entry`, used for ignored paths with the
+       `DIR_SHOW_IGNORED_TOO` and `DIR_COLLECT_IGNORED` flags.
+
+`ignored_nr`::
+
+       The number of members in `ignored[]` array.
 
 Calling sequence
 ----------------
index 54cd2d417d30408c8a4b13099efc6eee6a3d1e31..d4b40f2b7a9dbdabcb0133d84b29d8ab281d1baf 100644 (file)
@@ -545,9 +545,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
 
        if (pathspec) {
                int i;
-               struct path_exclude_check check;
 
-               path_exclude_check_init(&check, &dir);
                if (!seen)
                        seen = find_pathspecs_matching_against_index(pathspec);
                for (i = 0; pathspec[i]; i++) {
@@ -555,7 +553,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
                            && !file_exists(pathspec[i])) {
                                if (ignore_missing) {
                                        int dtype = DT_UNKNOWN;
-                                       if (is_path_excluded(&check, pathspec[i], -1, &dtype))
+                                       if (is_excluded(&dir, pathspec[i], &dtype))
                                                dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i]));
                                } else
                                        die(_("pathspec '%s' did not match any files"),
@@ -563,7 +561,6 @@ int cmd_add(int argc, const char **argv, const char *prefix)
                        }
                }
                free(seen);
-               path_exclude_check_clear(&check);
        }
 
        plug_bulk_checkin();
index 0240f99b57a2f81320b84951bf6dc89ab60d6282..854a88a0568e2f0226d26a2d9ebc99d24765053b 100644 (file)
@@ -59,7 +59,6 @@ static int check_ignore(const char *prefix, const char **pathspec)
        const char *path, *full_path;
        char *seen;
        int num_ignored = 0, dtype = DT_UNKNOWN, i;
-       struct path_exclude_check check;
        struct exclude *exclude;
 
        /* read_cache() is only necessary so we can watch out for submodules. */
@@ -67,7 +66,6 @@ static int check_ignore(const char *prefix, const char **pathspec)
                die(_("index file corrupt"));
 
        memset(&dir, 0, sizeof(dir));
-       dir.flags |= DIR_COLLECT_IGNORED;
        setup_standard_excludes(&dir);
 
        if (!pathspec || !*pathspec) {
@@ -76,7 +74,6 @@ static int check_ignore(const char *prefix, const char **pathspec)
                return 0;
        }
 
-       path_exclude_check_init(&check, &dir);
        /*
         * look for pathspecs matching entries in the index, since these
         * should not be ignored, in order to be consistent with
@@ -90,8 +87,7 @@ static int check_ignore(const char *prefix, const char **pathspec)
                full_path = check_path_for_gitlink(full_path);
                die_if_path_beyond_symlink(full_path, prefix);
                if (!seen[i]) {
-                       exclude = last_exclude_matching_path(&check, full_path,
-                                                            -1, &dtype);
+                       exclude = last_exclude_matching(&dir, full_path, &dtype);
                        if (exclude) {
                                if (!quiet)
                                        output_exclude(path, exclude);
@@ -101,7 +97,6 @@ static int check_ignore(const char *prefix, const char **pathspec)
        }
        free(seen);
        clear_directory(&dir);
-       path_exclude_check_clear(&check);
 
        return num_ignored;
 }
index 175e6e3e72003387bfa4f78dbd0ed10c02c4e318..22020729cbc35dc5fe11d2a3194fe5994fe67ff9 100644 (file)
@@ -201,19 +201,15 @@ static void show_ru_info(void)
        }
 }
 
-static int ce_excluded(struct path_exclude_check *check, struct cache_entry *ce)
+static int ce_excluded(struct dir_struct *dir, struct cache_entry *ce)
 {
        int dtype = ce_to_dtype(ce);
-       return is_path_excluded(check, ce->name, ce_namelen(ce), &dtype);
+       return is_excluded(dir, ce->name, &dtype);
 }
 
 static void show_files(struct dir_struct *dir)
 {
        int i;
-       struct path_exclude_check check;
-
-       if ((dir->flags & DIR_SHOW_IGNORED))
-               path_exclude_check_init(&check, dir);
 
        /* For cached/deleted files we don't need to even do the readdir */
        if (show_others || show_killed) {
@@ -227,7 +223,7 @@ static void show_files(struct dir_struct *dir)
                for (i = 0; i < active_nr; i++) {
                        struct cache_entry *ce = active_cache[i];
                        if ((dir->flags & DIR_SHOW_IGNORED) &&
-                           !ce_excluded(&check, ce))
+                           !ce_excluded(dir, ce))
                                continue;
                        if (show_unmerged && !ce_stage(ce))
                                continue;
@@ -243,7 +239,7 @@ static void show_files(struct dir_struct *dir)
                        struct stat st;
                        int err;
                        if ((dir->flags & DIR_SHOW_IGNORED) &&
-                           !ce_excluded(&check, ce))
+                           !ce_excluded(dir, ce))
                                continue;
                        if (ce->ce_flags & CE_UPDATE)
                                continue;
@@ -256,9 +252,6 @@ static void show_files(struct dir_struct *dir)
                                show_ce_entry(tag_modified, ce);
                }
        }
-
-       if ((dir->flags & DIR_SHOW_IGNORED))
-               path_exclude_check_clear(&check);
 }
 
 /*
diff --git a/dir.c b/dir.c
index 91cfd996711f8b9523da92ff75088292e16d316c..a5926fbd1aeafd468860da7dbd3d8a5d5999a650 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -17,7 +17,21 @@ struct path_simplify {
        const char *path;
 };
 
-static int read_directory_recursive(struct dir_struct *dir, const char *path, int len,
+/*
+ * Tells read_directory_recursive how a file or directory should be treated.
+ * Values are ordered by significance, e.g. if a directory contains both
+ * excluded and untracked files, it is listed as untracked because
+ * path_untracked > path_excluded.
+ */
+enum path_treatment {
+       path_none = 0,
+       path_recurse,
+       path_excluded,
+       path_untracked
+};
+
+static enum path_treatment read_directory_recursive(struct dir_struct *dir,
+       const char *path, int len,
        int check_only, const struct path_simplify *simplify);
 static int get_dtype(struct dirent *de, const char *path, int len);
 
@@ -578,78 +592,6 @@ void add_excludes_from_file(struct dir_struct *dir, const char *fname)
                die("cannot use %s as an exclude file", fname);
 }
 
-/*
- * Loads the per-directory exclude list for the substring of base
- * which has a char length of baselen.
- */
-static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
-{
-       struct exclude_list_group *group;
-       struct exclude_list *el;
-       struct exclude_stack *stk = NULL;
-       int current;
-
-       if ((!dir->exclude_per_dir) ||
-           (baselen + strlen(dir->exclude_per_dir) >= PATH_MAX))
-               return; /* too long a path -- ignore */
-
-       group = &dir->exclude_list_group[EXC_DIRS];
-
-       /* Pop the exclude lists from the EXCL_DIRS exclude_list_group
-        * which originate from directories not in the prefix of the
-        * path being checked. */
-       while ((stk = dir->exclude_stack) != NULL) {
-               if (stk->baselen <= baselen &&
-                   !strncmp(dir->basebuf, base, stk->baselen))
-                       break;
-               el = &group->el[dir->exclude_stack->exclude_ix];
-               dir->exclude_stack = stk->prev;
-               free((char *)el->src); /* see strdup() below */
-               clear_exclude_list(el);
-               free(stk);
-               group->nr--;
-       }
-
-       /* Read from the parent directories and push them down. */
-       current = stk ? stk->baselen : -1;
-       while (current < baselen) {
-               struct exclude_stack *stk = xcalloc(1, sizeof(*stk));
-               const char *cp;
-
-               if (current < 0) {
-                       cp = base;
-                       current = 0;
-               }
-               else {
-                       cp = strchr(base + current + 1, '/');
-                       if (!cp)
-                               die("oops in prep_exclude");
-                       cp++;
-               }
-               stk->prev = dir->exclude_stack;
-               stk->baselen = cp - base;
-               memcpy(dir->basebuf + current, base + current,
-                      stk->baselen - current);
-               strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir);
-               /*
-                * dir->basebuf gets reused by the traversal, but we
-                * need fname to remain unchanged to ensure the src
-                * member of each struct exclude correctly
-                * back-references its source file.  Other invocations
-                * of add_exclude_list provide stable strings, so we
-                * strdup() and free() here in the caller.
-                */
-               el = add_exclude_list(dir, EXC_DIRS, strdup(dir->basebuf));
-               stk->exclude_ix = group->nr - 1;
-               add_excludes_from_file_to_list(dir->basebuf,
-                                              dir->basebuf, stk->baselen,
-                                              el, 1);
-               dir->exclude_stack = stk;
-               current = stk->baselen;
-       }
-       dir->basebuf[baselen] = '\0';
-}
-
 int match_basename(const char *basename, int basenamelen,
                   const char *pattern, int prefix, int patternlen,
                   int flags)
@@ -795,25 +737,13 @@ int is_excluded_from_list(const char *pathname,
        return -1; /* undecided */
 }
 
-/*
- * Loads the exclude lists for the directory containing pathname, then
- * scans all exclude lists to determine whether pathname is excluded.
- * Returns the exclude_list element which matched, or NULL for
- * undecided.
- */
-static struct exclude *last_exclude_matching(struct dir_struct *dir,
-                                            const char *pathname,
-                                            int *dtype_p)
+static struct exclude *last_exclude_matching_from_lists(struct dir_struct *dir,
+               const char *pathname, int pathlen, const char *basename,
+               int *dtype_p)
 {
-       int pathlen = strlen(pathname);
        int i, j;
        struct exclude_list_group *group;
        struct exclude *exclude;
-       const char *basename = strrchr(pathname, '/');
-       basename = (basename) ? basename+1 : pathname;
-
-       prep_exclude(dir, pathname, basename-pathname);
-
        for (i = EXC_CMDL; i <= EXC_FILE; i++) {
                group = &dir->exclude_list_group[i];
                for (j = group->nr - 1; j >= 0; j--) {
@@ -828,101 +758,131 @@ static struct exclude *last_exclude_matching(struct dir_struct *dir,
 }
 
 /*
- * Loads the exclude lists for the directory containing pathname, then
- * scans all exclude lists to determine whether pathname is excluded.
- * Returns 1 if true, otherwise 0.
+ * Loads the per-directory exclude list for the substring of base
+ * which has a char length of baselen.
  */
-static int is_excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
-{
-       struct exclude *exclude =
-               last_exclude_matching(dir, pathname, dtype_p);
-       if (exclude)
-               return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
-       return 0;
-}
-
-void path_exclude_check_init(struct path_exclude_check *check,
-                            struct dir_struct *dir)
+static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
 {
-       check->dir = dir;
-       check->exclude = NULL;
-       strbuf_init(&check->path, 256);
-}
+       struct exclude_list_group *group;
+       struct exclude_list *el;
+       struct exclude_stack *stk = NULL;
+       int current;
 
-void path_exclude_check_clear(struct path_exclude_check *check)
-{
-       strbuf_release(&check->path);
-}
+       group = &dir->exclude_list_group[EXC_DIRS];
 
-/*
- * For each subdirectory in name, starting with the top-most, checks
- * to see if that subdirectory is excluded, and if so, returns the
- * corresponding exclude structure.  Otherwise, checks whether name
- * itself (which is presumably a file) is excluded.
- *
- * A path to a directory known to be excluded is left in check->path to
- * optimize for repeated checks for files in the same excluded directory.
- */
-struct exclude *last_exclude_matching_path(struct path_exclude_check *check,
-                                          const char *name, int namelen,
-                                          int *dtype)
-{
-       int i;
-       struct strbuf *path = &check->path;
-       struct exclude *exclude;
+       /* Pop the exclude lists from the EXCL_DIRS exclude_list_group
+        * which originate from directories not in the prefix of the
+        * path being checked. */
+       while ((stk = dir->exclude_stack) != NULL) {
+               if (stk->baselen <= baselen &&
+                   !strncmp(dir->basebuf, base, stk->baselen))
+                       break;
+               el = &group->el[dir->exclude_stack->exclude_ix];
+               dir->exclude_stack = stk->prev;
+               dir->exclude = NULL;
+               free((char *)el->src); /* see strdup() below */
+               clear_exclude_list(el);
+               free(stk);
+               group->nr--;
+       }
 
-       /*
-        * we allow the caller to pass namelen as an optimization; it
-        * must match the length of the name, as we eventually call
-        * is_excluded() on the whole name string.
-        */
-       if (namelen < 0)
-               namelen = strlen(name);
+       /* Skip traversing into sub directories if the parent is excluded */
+       if (dir->exclude)
+               return;
 
-       /*
-        * If path is non-empty, and name is equal to path or a
-        * subdirectory of path, name should be excluded, because
-        * it's inside a directory which is already known to be
-        * excluded and was previously left in check->path.
-        */
-       if (path->len &&
-           path->len <= namelen &&
-           !memcmp(name, path->buf, path->len) &&
-           (!name[path->len] || name[path->len] == '/'))
-               return check->exclude;
+       /* Read from the parent directories and push them down. */
+       current = stk ? stk->baselen : -1;
+       while (current < baselen) {
+               struct exclude_stack *stk = xcalloc(1, sizeof(*stk));
+               const char *cp;
 
-       strbuf_setlen(path, 0);
-       for (i = 0; name[i]; i++) {
-               int ch = name[i];
+               if (current < 0) {
+                       cp = base;
+                       current = 0;
+               }
+               else {
+                       cp = strchr(base + current + 1, '/');
+                       if (!cp)
+                               die("oops in prep_exclude");
+                       cp++;
+               }
+               stk->prev = dir->exclude_stack;
+               stk->baselen = cp - base;
+               stk->exclude_ix = group->nr;
+               el = add_exclude_list(dir, EXC_DIRS, NULL);
+               memcpy(dir->basebuf + current, base + current,
+                      stk->baselen - current);
 
-               if (ch == '/') {
+               /* Abort if the directory is excluded */
+               if (stk->baselen) {
                        int dt = DT_DIR;
-                       exclude = last_exclude_matching(check->dir,
-                                                       path->buf, &dt);
-                       if (exclude) {
-                               check->exclude = exclude;
-                               return exclude;
+                       dir->basebuf[stk->baselen - 1] = 0;
+                       dir->exclude = last_exclude_matching_from_lists(dir,
+                               dir->basebuf, stk->baselen - 1,
+                               dir->basebuf + current, &dt);
+                       dir->basebuf[stk->baselen - 1] = '/';
+                       if (dir->exclude) {
+                               dir->basebuf[stk->baselen] = 0;
+                               dir->exclude_stack = stk;
+                               return;
                        }
                }
-               strbuf_addch(path, ch);
+
+               /* Try to read per-directory file unless path is too long */
+               if (dir->exclude_per_dir &&
+                   stk->baselen + strlen(dir->exclude_per_dir) < PATH_MAX) {
+                       strcpy(dir->basebuf + stk->baselen,
+                                       dir->exclude_per_dir);
+                       /*
+                        * dir->basebuf gets reused by the traversal, but we
+                        * need fname to remain unchanged to ensure the src
+                        * member of each struct exclude correctly
+                        * back-references its source file.  Other invocations
+                        * of add_exclude_list provide stable strings, so we
+                        * strdup() and free() here in the caller.
+                        */
+                       el->src = strdup(dir->basebuf);
+                       add_excludes_from_file_to_list(dir->basebuf,
+                                       dir->basebuf, stk->baselen, el, 1);
+               }
+               dir->exclude_stack = stk;
+               current = stk->baselen;
        }
+       dir->basebuf[baselen] = '\0';
+}
 
-       /* An entry in the index; cannot be a directory with subentries */
-       strbuf_setlen(path, 0);
+/*
+ * Loads the exclude lists for the directory containing pathname, then
+ * scans all exclude lists to determine whether pathname is excluded.
+ * Returns the exclude_list element which matched, or NULL for
+ * undecided.
+ */
+struct exclude *last_exclude_matching(struct dir_struct *dir,
+                                            const char *pathname,
+                                            int *dtype_p)
+{
+       int pathlen = strlen(pathname);
+       const char *basename = strrchr(pathname, '/');
+       basename = (basename) ? basename+1 : pathname;
+
+       prep_exclude(dir, pathname, basename-pathname);
 
-       return last_exclude_matching(check->dir, name, dtype);
+       if (dir->exclude)
+               return dir->exclude;
+
+       return last_exclude_matching_from_lists(dir, pathname, pathlen,
+                       basename, dtype_p);
 }
 
 /*
- * Is this name excluded?  This is for a caller like show_files() that
- * do not honor directory hierarchy and iterate through paths that are
- * possibly in an ignored directory.
+ * Loads the exclude lists for the directory containing pathname, then
+ * scans all exclude lists to determine whether pathname is excluded.
+ * Returns 1 if true, otherwise 0.
  */
-int is_path_excluded(struct path_exclude_check *check,
-                 const char *name, int namelen, int *dtype)
+int is_excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
 {
        struct exclude *exclude =
-               last_exclude_matching_path(check, name, namelen, dtype);
+               last_exclude_matching(dir, pathname, dtype_p);
        if (exclude)
                return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
        return 0;
@@ -941,8 +901,7 @@ static struct dir_entry *dir_entry_new(const char *pathname, int len)
 
 static struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len)
 {
-       if (!(dir->flags & DIR_SHOW_IGNORED) &&
-           cache_name_exists(pathname, len, ignore_case))
+       if (cache_name_exists(pathname, len, ignore_case))
                return NULL;
 
        ALLOC_GROW(dir->entries, dir->nr+1, dir->alloc);
@@ -1044,9 +1003,8 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len)
  * traversal routine.
  *
  * Case 1: If we *already* have entries in the index under that
- * directory name, we recurse into the directory to see all the files,
- * unless the directory is excluded and we want to show ignored
- * directories
+ * directory name, we always recurse into the directory to see
+ * all the files.
  *
  * Case 2: If we *already* have that directory name as a gitlink,
  * we always continue to see it as a gitlink, regardless of whether
@@ -1058,38 +1016,26 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len)
  *
  *  (a) if "show_other_directories" is true, we show it as
  *      just a directory, unless "hide_empty_directories" is
- *      also true and the directory is empty, in which case
- *      we just ignore it entirely.
- *      if we are looking for ignored directories, look if it
- *      contains only ignored files to decide if it must be shown as
- *      ignored or not.
+ *      also true, in which case we need to check if it contains any
+ *      untracked and / or ignored files.
  *  (b) if it looks like a git directory, and we don't have
  *      'no_gitlinks' set we treat it as a gitlink, and show it
  *      as a directory.
  *  (c) otherwise, we recurse into it.
  */
-enum directory_treatment {
-       show_directory,
-       ignore_directory,
-       recurse_into_directory
-};
-
-static enum directory_treatment treat_directory(struct dir_struct *dir,
+static enum path_treatment treat_directory(struct dir_struct *dir,
        const char *dirname, int len, int exclude,
        const struct path_simplify *simplify)
 {
        /* The "len-1" is to strip the final '/' */
        switch (directory_exists_in_index(dirname, len-1)) {
        case index_directory:
-               if ((dir->flags & DIR_SHOW_OTHER_DIRECTORIES) && exclude)
-                       break;
-
-               return recurse_into_directory;
+               return path_recurse;
 
        case index_gitdir:
                if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
-                       return ignore_directory;
-               return show_directory;
+                       return path_none;
+               return path_untracked;
 
        case index_nonexistent:
                if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
@@ -1097,72 +1043,17 @@ static enum directory_treatment treat_directory(struct dir_struct *dir,
                if (!(dir->flags & DIR_NO_GITLINKS)) {
                        unsigned char sha1[20];
                        if (resolve_gitlink_ref(dirname, "HEAD", sha1) == 0)
-                               return show_directory;
+                               return path_untracked;
                }
-               return recurse_into_directory;
+               return path_recurse;
        }
 
        /* This is the "show_other_directories" case */
 
-       /*
-        * We are looking for ignored files and our directory is not ignored,
-        * check if it contains only ignored files
-        */
-       if ((dir->flags & DIR_SHOW_IGNORED) && !exclude) {
-               int ignored;
-               dir->flags &= ~DIR_SHOW_IGNORED;
-               dir->flags |= DIR_HIDE_EMPTY_DIRECTORIES;
-               ignored = read_directory_recursive(dir, dirname, len, 1, simplify);
-               dir->flags &= ~DIR_HIDE_EMPTY_DIRECTORIES;
-               dir->flags |= DIR_SHOW_IGNORED;
-
-               return ignored ? ignore_directory : show_directory;
-       }
-       if (!(dir->flags & DIR_SHOW_IGNORED) &&
-           !(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
-               return show_directory;
-       if (!read_directory_recursive(dir, dirname, len, 1, simplify))
-               return ignore_directory;
-       return show_directory;
-}
-
-/*
- * Decide what to do when we find a file while traversing the
- * filesystem. Mostly two cases:
- *
- *  1. We are looking for ignored files
- *   (a) File is ignored, include it
- *   (b) File is in ignored path, include it
- *   (c) File is not ignored, exclude it
- *
- *  2. Other scenarios, include the file if not excluded
- *
- * Return 1 for exclude, 0 for include.
- */
-static int treat_file(struct dir_struct *dir, struct strbuf *path, int exclude, int *dtype)
-{
-       struct path_exclude_check check;
-       int exclude_file = 0;
-
-       if (exclude)
-               exclude_file = !(dir->flags & DIR_SHOW_IGNORED);
-       else if (dir->flags & DIR_SHOW_IGNORED) {
-               /* Always exclude indexed files */
-               struct cache_entry *ce = index_name_exists(&the_index,
-                   path->buf, path->len, ignore_case);
-
-               if (ce)
-                       return 1;
-
-               path_exclude_check_init(&check, dir);
-
-               if (!is_path_excluded(&check, path->buf, path->len, dtype))
-                       exclude_file = 1;
-
-               path_exclude_check_clear(&check);
-       }
+       if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
+               return exclude ? path_excluded : path_untracked;
 
-       return exclude_file;
+       return read_directory_recursive(dir, dirname, len, 1, simplify);
 }
 
 /*
@@ -1277,57 +1168,40 @@ static int get_dtype(struct dirent *de, const char *path, int len)
        return dtype;
 }
 
-enum path_treatment {
-       path_ignored,
-       path_handled,
-       path_recurse
-};
-
 static enum path_treatment treat_one_path(struct dir_struct *dir,
                                          struct strbuf *path,
                                          const struct path_simplify *simplify,
                                          int dtype, struct dirent *de)
 {
-       int exclude = is_excluded(dir, path->buf, &dtype);
-       if (exclude && (dir->flags & DIR_COLLECT_IGNORED)
-           && exclude_matches_pathspec(path->buf, path->len, simplify))
-               dir_add_ignored(dir, path->buf, path->len);
+       int exclude;
+       if (dtype == DT_UNKNOWN)
+               dtype = get_dtype(de, path->buf, path->len);
+
+       /* Always exclude indexed files */
+       if (dtype != DT_DIR &&
+           cache_name_exists(path->buf, path->len, ignore_case))
+               return path_none;
+
+       exclude = is_excluded(dir, path->buf, &dtype);
 
        /*
         * Excluded? If we don't explicitly want to show
         * ignored files, ignore it
         */
-       if (exclude && !(dir->flags & DIR_SHOW_IGNORED))
-               return path_ignored;
-
-       if (dtype == DT_UNKNOWN)
-               dtype = get_dtype(de, path->buf, path->len);
+       if (exclude && !(dir->flags & (DIR_SHOW_IGNORED|DIR_SHOW_IGNORED_TOO)))
+               return path_excluded;
 
        switch (dtype) {
        default:
-               return path_ignored;
+               return path_none;
        case DT_DIR:
                strbuf_addch(path, '/');
-
-               switch (treat_directory(dir, path->buf, path->len, exclude, simplify)) {
-               case show_directory:
-                       break;
-               case recurse_into_directory:
-                       return path_recurse;
-               case ignore_directory:
-                       return path_ignored;
-               }
-               break;
+               return treat_directory(dir, path->buf, path->len, exclude,
+                       simplify);
        case DT_REG:
        case DT_LNK:
-               switch (treat_file(dir, path, exclude, &dtype)) {
-               case 1:
-                       return path_ignored;
-               default:
-                       break;
-               }
+               return exclude ? path_excluded : path_untracked;
        }
-       return path_handled;
 }
 
 static enum path_treatment treat_path(struct dir_struct *dir,
@@ -1339,11 +1213,11 @@ static enum path_treatment treat_path(struct dir_struct *dir,
        int dtype;
 
        if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git"))
-               return path_ignored;
+               return path_none;
        strbuf_setlen(path, baselen);
        strbuf_addstr(path, de->d_name);
        if (simplify_away(path->buf, path->len, simplify))
-               return path_ignored;
+               return path_none;
 
        dtype = DTYPE(de);
        return treat_one_path(dir, path, simplify, dtype, de);
@@ -1357,14 +1231,16 @@ static enum path_treatment treat_path(struct dir_struct *dir,
  *
  * Also, we ignore the name ".git" (even if it is not a directory).
  * That likely will not change.
+ *
+ * Returns the most significant path_treatment value encountered in the scan.
  */
-static int read_directory_recursive(struct dir_struct *dir,
+static enum path_treatment read_directory_recursive(struct dir_struct *dir,
                                    const char *base, int baselen,
                                    int check_only,
                                    const struct path_simplify *simplify)
 {
        DIR *fdir;
-       int contents = 0;
+       enum path_treatment state, subdir_state, dir_state = path_none;
        struct dirent *de;
        struct strbuf path = STRBUF_INIT;
 
@@ -1375,27 +1251,53 @@ static int read_directory_recursive(struct dir_struct *dir,
                goto out;
 
        while ((de = readdir(fdir)) != NULL) {
-               switch (treat_path(dir, de, &path, baselen, simplify)) {
-               case path_recurse:
-                       contents += read_directory_recursive(dir, path.buf,
-                                                            path.len, 0,
-                                                            simplify);
-                       continue;
-               case path_ignored:
+               /* check how the file or directory should be treated */
+               state = treat_path(dir, de, &path, baselen, simplify);
+               if (state > dir_state)
+                       dir_state = state;
+
+               /* recurse into subdir if instructed by treat_path */
+               if (state == path_recurse) {
+                       subdir_state = read_directory_recursive(dir, path.buf,
+                               path.len, check_only, simplify);
+                       if (subdir_state > dir_state)
+                               dir_state = subdir_state;
+               }
+
+               if (check_only) {
+                       /* abort early if maximum state has been reached */
+                       if (dir_state == path_untracked)
+                               break;
+                       /* skip the dir_add_* part */
                        continue;
-               case path_handled:
-                       break;
                }
-               contents++;
-               if (check_only)
+
+               /* add the path to the appropriate result list */
+               switch (state) {
+               case path_excluded:
+                       if (dir->flags & DIR_SHOW_IGNORED)
+                               dir_add_name(dir, path.buf, path.len);
+                       else if ((dir->flags & DIR_SHOW_IGNORED_TOO) ||
+                               ((dir->flags & DIR_COLLECT_IGNORED) &&
+                               exclude_matches_pathspec(path.buf, path.len,
+                                       simplify)))
+                               dir_add_ignored(dir, path.buf, path.len);
                        break;
-               dir_add_name(dir, path.buf, path.len);
+
+               case path_untracked:
+                       if (!(dir->flags & DIR_SHOW_IGNORED))
+                               dir_add_name(dir, path.buf, path.len);
+                       break;
+
+               default:
+                       break;
+               }
        }
        closedir(fdir);
  out:
        strbuf_release(&path);
 
-       return contents;
+       return dir_state;
 }
 
 static int cmp_name(const void *p1, const void *p2)
@@ -1444,12 +1346,14 @@ static int treat_leading_path(struct dir_struct *dir,
        struct strbuf sb = STRBUF_INIT;
        int baselen, rc = 0;
        const char *cp;
+       int old_flags = dir->flags;
 
        while (len && path[len - 1] == '/')
                len--;
        if (!len)
                return 1;
        baselen = 0;
+       dir->flags &= ~DIR_SHOW_OTHER_DIRECTORIES;
        while (1) {
                cp = path + baselen + !!baselen;
                cp = memchr(cp, '/', path + len - cp);
@@ -1464,7 +1368,7 @@ static int treat_leading_path(struct dir_struct *dir,
                if (simplify_away(sb.buf, sb.len, simplify))
                        break;
                if (treat_one_path(dir, &sb, simplify,
-                                  DT_DIR, NULL) == path_ignored)
+                                  DT_DIR, NULL) == path_none)
                        break; /* do not recurse into it */
                if (len <= baselen) {
                        rc = 1;
@@ -1472,6 +1376,7 @@ static int treat_leading_path(struct dir_struct *dir,
                }
        }
        strbuf_release(&sb);
+       dir->flags = old_flags;
        return rc;
 }
 
diff --git a/dir.h b/dir.h
index c3eb4b520effb63e73653194e4266c0ef628cccd..3d6b80c933b395644c005f6d8df4697215ff000a 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -79,7 +79,8 @@ struct dir_struct {
                DIR_SHOW_OTHER_DIRECTORIES = 1<<1,
                DIR_HIDE_EMPTY_DIRECTORIES = 1<<2,
                DIR_NO_GITLINKS = 1<<3,
-               DIR_COLLECT_IGNORED = 1<<4
+               DIR_COLLECT_IGNORED = 1<<4,
+               DIR_SHOW_IGNORED_TOO = 1<<5
        } flags;
        struct dir_entry **entries;
        struct dir_entry **ignored;
@@ -110,9 +111,11 @@ struct dir_struct {
         *
         * exclude_stack points to the top of the exclude_stack, and
         * basebuf contains the full path to the current
-        * (sub)directory in the traversal.
+        * (sub)directory in the traversal. Exclude points to the
+        * matching exclude struct if the directory is excluded.
         */
        struct exclude_stack *exclude_stack;
+       struct exclude *exclude;
        char basebuf[PATH_MAX];
 };
 
@@ -149,22 +152,10 @@ extern int match_pathname(const char *, int,
                          const char *, int,
                          const char *, int, int, int);
 
-/*
- * The is_excluded() API is meant for callers that check each level of leading
- * directory hierarchies with is_excluded() to avoid recursing into excluded
- * directories.  Callers that do not do so should use this API instead.
- */
-struct path_exclude_check {
-       struct dir_struct *dir;
-       struct exclude *exclude;
-       struct strbuf path;
-};
-extern void path_exclude_check_init(struct path_exclude_check *, struct dir_struct *);
-extern void path_exclude_check_clear(struct path_exclude_check *);
-extern struct exclude *last_exclude_matching_path(struct path_exclude_check *, const char *,
-                                                 int namelen, int *dtype);
-extern int is_path_excluded(struct path_exclude_check *, const char *, int namelen, int *dtype);
+extern struct exclude *last_exclude_matching(struct dir_struct *dir,
+                                            const char *name, int *dtype);
 
+extern int is_excluded(struct dir_struct *dir, const char *name, int *dtype);
 
 extern struct exclude_list *add_exclude_list(struct dir_struct *dir,
                                             int group_type, const char *src);
index 2d274bf8ff2b143e11382586966915dcb0d99d25..4e3735f0cb28d2b545f59a2464ea74bea4b626a1 100755 (executable)
@@ -214,6 +214,55 @@ test_expect_success 'subdirectory ignore (l1)' '
        test_cmp expect actual
 '
 
+test_expect_success 'show/hide empty ignored directory (setup)' '
+       rm top/l1/l2/l1 &&
+       rm top/l1/.gitignore
+'
+
+test_expect_success 'show empty ignored directory with --directory' '
+       (
+               cd top &&
+               git ls-files -o -i --exclude l1 --directory
+       ) >actual &&
+       echo l1/ >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'hide empty ignored directory with --no-empty-directory' '
+       (
+               cd top &&
+               git ls-files -o -i --exclude l1 --directory --no-empty-directory
+       ) >actual &&
+       >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'show/hide empty ignored sub-directory (setup)' '
+       > top/l1/tracked &&
+       (
+               cd top &&
+               git add -f l1/tracked
+       )
+'
+
+test_expect_success 'show empty ignored sub-directory with --directory' '
+       (
+               cd top &&
+               git ls-files -o -i --exclude l1 --directory
+       ) >actual &&
+       echo l1/l2/ >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'hide empty ignored sub-directory with --no-empty-directory' '
+       (
+               cd top &&
+               git ls-files -o -i --exclude l1 --directory --no-empty-directory
+       ) >actual &&
+       >expect &&
+       test_cmp expect actual
+'
+
 test_expect_success 'pattern matches prefix completely' '
        : >expect &&
        git ls-files -i -o --exclude "/three/a.3[abc]" >actual &&
index 0da1214bcca439c126a0e19c664ed9230a1001fe..460789b4d85241eb51c0c1a5a1f4be7d4a0d7154 100755 (executable)
@@ -32,6 +32,25 @@ test_expect_success 'status untracked directory with --ignored -u' '
        git status --porcelain --ignored -u >actual &&
        test_cmp expected actual
 '
+cat >expected <<\EOF
+?? untracked/uncommitted
+!! untracked/ignored
+EOF
+
+test_expect_success 'status prefixed untracked directory with --ignored' '
+       git status --porcelain --ignored untracked/ >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<\EOF
+?? untracked/uncommitted
+!! untracked/ignored
+EOF
+
+test_expect_success 'status prefixed untracked sub-directory with --ignored -u' '
+       git status --porcelain --ignored -u untracked/ >actual &&
+       test_cmp expected actual
+'
 
 cat >expected <<\EOF
 ?? .gitignore
@@ -64,13 +83,35 @@ cat >expected <<\EOF
 ?? .gitignore
 ?? actual
 ?? expected
-!! untracked-ignored/
 EOF
 
-test_expect_success 'status untracked directory with ignored files with --ignore' '
+test_expect_success 'status empty untracked directory with --ignore' '
        rm -rf ignored &&
        mkdir untracked-ignored &&
        mkdir untracked-ignored/test &&
+       git status --porcelain --ignored >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<\EOF
+?? .gitignore
+?? actual
+?? expected
+EOF
+
+test_expect_success 'status empty untracked directory with --ignore -u' '
+       git status --porcelain --ignored -u >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<\EOF
+?? .gitignore
+?? actual
+?? expected
+!! untracked-ignored/
+EOF
+
+test_expect_success 'status untracked directory with ignored files with --ignore' '
        : >untracked-ignored/ignored &&
        : >untracked-ignored/test/ignored &&
        git status --porcelain --ignored >actual &&
@@ -122,10 +163,34 @@ cat >expected <<\EOF
 ?? .gitignore
 ?? actual
 ?? expected
-!! tracked/
+EOF
+
+test_expect_success 'status ignored tracked directory and ignored file with --ignore' '
+       echo "committed" >>.gitignore &&
+       git status --porcelain --ignored >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<\EOF
+?? .gitignore
+?? actual
+?? expected
+EOF
+
+test_expect_success 'status ignored tracked directory and ignored file with --ignore -u' '
+       git status --porcelain --ignored -u >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<\EOF
+?? .gitignore
+?? actual
+?? expected
+!! tracked/uncommitted
 EOF
 
 test_expect_success 'status ignored tracked directory and uncommitted file with --ignore' '
+       echo "tracked" >.gitignore &&
        : >tracked/uncommitted &&
        git status --porcelain --ignored >actual &&
        test_cmp expected actual
@@ -143,4 +208,58 @@ test_expect_success 'status ignored tracked directory and uncommitted file with
        test_cmp expected actual
 '
 
+cat >expected <<\EOF
+?? .gitignore
+?? actual
+?? expected
+!! tracked/ignored/
+EOF
+
+test_expect_success 'status ignored tracked directory with uncommitted file in untracked subdir with --ignore' '
+       rm -rf tracked/uncommitted &&
+       mkdir tracked/ignored &&
+       : >tracked/ignored/uncommitted &&
+       git status --porcelain --ignored >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<\EOF
+?? .gitignore
+?? actual
+?? expected
+!! tracked/ignored/uncommitted
+EOF
+
+test_expect_success 'status ignored tracked directory with uncommitted file in untracked subdir with --ignore -u' '
+       git status --porcelain --ignored -u >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<\EOF
+?? .gitignore
+?? actual
+?? expected
+!! tracked/ignored/uncommitted
+EOF
+
+test_expect_success 'status ignored tracked directory with uncommitted file in tracked subdir with --ignore' '
+       : >tracked/ignored/committed &&
+       git add -f tracked/ignored/committed &&
+       git commit -m. &&
+       git status --porcelain --ignored >actual &&
+       test_cmp expected actual
+'
+
+cat >expected <<\EOF
+?? .gitignore
+?? actual
+?? expected
+!! tracked/ignored/uncommitted
+EOF
+
+test_expect_success 'status ignored tracked directory with uncommitted file in tracked subdir with --ignore -u' '
+       git status --porcelain --ignored -u >actual &&
+       test_cmp expected actual
+'
+
 test_done
index ccfb54de7ad9473221390d019b109bcb010a2c76..710be90489b2fd5d6ca1a98201c1ec635efcae9f 100755 (executable)
@@ -298,6 +298,23 @@ test_expect_success 'git clean -d -x' '
 
 '
 
+test_expect_success 'git clean -d -x with ignored tracked directory' '
+
+       mkdir -p build docs &&
+       touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
+       git clean -d -x -e src &&
+       test -f Makefile &&
+       test -f README &&
+       test -f src/part1.c &&
+       test -f src/part2.c &&
+       test ! -f a.out &&
+       test -f src/part3.c &&
+       test ! -d docs &&
+       test ! -f obj.o &&
+       test ! -d build
+
+'
+
 test_expect_success 'git clean -X' '
 
        mkdir -p build docs &&
@@ -332,6 +349,23 @@ test_expect_success 'git clean -d -X' '
 
 '
 
+test_expect_success 'git clean -d -X with ignored tracked directory' '
+
+       mkdir -p build docs &&
+       touch a.out src/part3.c docs/manual.txt obj.o build/lib.so &&
+       git clean -d -X -e src &&
+       test -f Makefile &&
+       test -f README &&
+       test -f src/part1.c &&
+       test -f src/part2.c &&
+       test -f a.out &&
+       test ! -f src/part3.c &&
+       test -f docs/manual.txt &&
+       test ! -f obj.o &&
+       test ! -d build
+
+'
+
 test_expect_success 'clean.requireForce defaults to true' '
 
        git config --unset clean.requireForce &&
index 09e53df3b2a39e7ec8c7d601c5b8b8ae4de39b9a..ede4299b833378ef5a4d44dac181d54889343a27 100644 (file)
@@ -1026,10 +1026,6 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
                        o->el = &el;
        }
 
-       if (o->dir) {
-               o->path_exclude_check = xmalloc(sizeof(struct path_exclude_check));
-               path_exclude_check_init(o->path_exclude_check, o->dir);
-       }
        memset(&o->result, 0, sizeof(o->result));
        o->result.initialized = 1;
        o->result.timestamp.sec = o->src_index->timestamp.sec;
@@ -1155,10 +1151,6 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
 
 done:
        clear_exclude_list(&el);
-       if (o->path_exclude_check) {
-               path_exclude_check_clear(o->path_exclude_check);
-               free(o->path_exclude_check);
-       }
        return ret;
 
 return_failed:
@@ -1375,7 +1367,7 @@ static int check_ok_to_remove(const char *name, int len, int dtype,
                return 0;
 
        if (o->dir &&
-           is_path_excluded(o->path_exclude_check, name, -1, &dtype))
+           is_excluded(o->dir, name, &dtype))
                /*
                 * ce->name is explicitly excluded, so it is Ok to
                 * overwrite it.
index ec74a9f19a47c39de61def9709da6d4d6f1dcbdb..5e432f576eb2304a63510a61a71182e11f777092 100644 (file)
@@ -52,7 +52,6 @@ struct unpack_trees_options {
        const char *prefix;
        int cache_bottom;
        struct dir_struct *dir;
-       struct path_exclude_check *path_exclude_check;
        struct pathspec *pathspec;
        merge_fn_t fn;
        const char *msgs[NB_UNPACK_TREES_ERROR_TYPES];
index ec5f27c5992e5fec5fb54384a04ffacf1745ce4a..bf84a86ee3d6b136bd83115f0bf15a55835817fc 100644 (file)
@@ -511,9 +511,12 @@ static void wt_status_collect_untracked(struct wt_status *s)
        if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)
                dir.flags |=
                        DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
+       if (s->show_ignored_files)
+               dir.flags |= DIR_SHOW_IGNORED_TOO;
        setup_standard_excludes(&dir);
 
        fill_directory(&dir, s->pathspec);
+
        for (i = 0; i < dir.nr; i++) {
                struct dir_entry *ent = dir.entries[i];
                if (cache_name_is_other(ent->name, ent->len) &&
@@ -522,22 +525,17 @@ static void wt_status_collect_untracked(struct wt_status *s)
                free(ent);
        }
 
-       if (s->show_ignored_files) {
-               dir.nr = 0;
-               dir.flags = DIR_SHOW_IGNORED;
-               if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)
-                       dir.flags |= DIR_SHOW_OTHER_DIRECTORIES;
-               fill_directory(&dir, s->pathspec);
-               for (i = 0; i < dir.nr; i++) {
-                       struct dir_entry *ent = dir.entries[i];
-                       if (cache_name_is_other(ent->name, ent->len) &&
-                           match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
-                               string_list_insert(&s->ignored, ent->name);
-                       free(ent);
-               }
+       for (i = 0; i < dir.ignored_nr; i++) {
+               struct dir_entry *ent = dir.ignored[i];
+               if (cache_name_is_other(ent->name, ent->len) &&
+                   match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
+                       string_list_insert(&s->ignored, ent->name);
+               free(ent);
        }
 
        free(dir.entries);
+       free(dir.ignored);
+       clear_directory(&dir);
 
        if (advice_status_u_option) {
                struct timeval t_end;