]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'en/clean-nested-with-ignored'
authorJunio C Hamano <gitster@pobox.com>
Fri, 11 Oct 2019 05:24:45 +0000 (14:24 +0900)
committerJunio C Hamano <gitster@pobox.com>
Fri, 11 Oct 2019 05:24:46 +0000 (14:24 +0900)
"git clean" fixes.

* en/clean-nested-with-ignored:
  dir: special case check for the possibility that pathspec is NULL
  clean: fix theoretical path corruption
  clean: rewrap overly long line
  clean: avoid removing untracked files in a nested git repository
  clean: disambiguate the definition of -d
  git-clean.txt: do not claim we will delete files with -n/--dry-run
  dir: add commentary explaining match_pathspec_item's return value
  dir: if our pathspec might match files under a dir, recurse into it
  dir: make the DO_MATCH_SUBMODULE code reusable for a non-submodule case
  dir: also check directories for matching pathspecs
  dir: fix off-by-one error in match_pathspec_item
  dir: fix typo in comment
  t7300: add testcases showing failure to clean specified pathspecs

1  2 
Documentation/git-clean.txt
builtin/clean.c
dir.c
dir.h

index 0028ff12d1dadb2abc7a50816ae6fa5807c82f46,ba31d8d166c34c4cd6018ad6d347a8c6ece3d1ff..a7f309dff5a327ba14b38b63b931060582c53c28
@@@ -26,18 -26,20 +26,20 @@@ are affected
  OPTIONS
  -------
  -d::
-       Remove untracked directories in addition to untracked files.
-       If an untracked directory is managed by a different Git
-       repository, it is not removed by default.  Use -f option twice
-       if you really want to remove such a directory.
+       Normally, when no <path> is specified, git clean will not
+       recurse into untracked directories to avoid removing too much.
+       Specify -d to have it recurse into such directories as well.
+       If any paths are specified, -d is irrelevant; all untracked
+       files matching the specified paths (with exceptions for nested
+       git directories mentioned under `--force`) will be removed.
  
  -f::
  --force::
        If the Git configuration variable clean.requireForce is not set
        to false, 'git clean' will refuse to delete files or directories
-       unless given -f, -n or -i. Git will refuse to delete directories
-       with .git sub directory or file unless a second -f
-       is given.
+       unless given -f or -i.  Git will refuse to modify untracked
+       nested git repositories (directories with a .git subdirectory)
+       unless a second -f is given.
  
  -i::
  --interactive::
@@@ -63,7 -65,7 +65,7 @@@
        still use the ignore rules given with `-e` options from the command
        line.  This allows removing all untracked
        files, including build products.  This can be used (possibly in
 -      conjunction with 'git reset') to create a pristine
 +      conjunction with 'git restore' or 'git reset') to create a pristine
        working directory to test a clean build.
  
  -X::
diff --combined builtin/clean.c
index 851beb7f0debb4322122e9aa40aa642fe4ff9be2,4cf2399f59fb8fba6641d72c509aebe5813b9514..5abf087e7c495153b36b927b5b615f80a68d414c
@@@ -158,7 -158,8 +158,8 @@@ static int remove_dirs(struct strbuf *p
  
        *dir_gone = 1;
  
-       if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) && is_nonbare_repository_dir(path)) {
+       if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
+           is_nonbare_repository_dir(path)) {
                if (!quiet) {
                        quote_path_relative(path->buf, prefix, &quoted);
                        printf(dry_run ?  _(msg_would_skip_git_dir) : _(msg_skip_git_dir),
@@@ -648,7 -649,7 +649,7 @@@ static int filter_by_patterns_cmd(void
        struct strbuf confirm = STRBUF_INIT;
        struct strbuf **ignore_list;
        struct string_list_item *item;
 -      struct exclude_list *el;
 +      struct pattern_list *pl;
        int changed = -1, i;
  
        for (;;) {
                        break;
  
                memset(&dir, 0, sizeof(dir));
 -              el = add_exclude_list(&dir, EXC_CMDL, "manual exclude");
 +              pl = add_pattern_list(&dir, EXC_CMDL, "manual exclude");
                ignore_list = strbuf_split_max(&confirm, ' ', 0);
  
                for (i = 0; ignore_list[i]; i++) {
                        if (!ignore_list[i]->len)
                                continue;
  
 -                      add_exclude(ignore_list[i]->buf, "", 0, el, -(i+1));
 +                      add_pattern(ignore_list[i]->buf, "", 0, pl, -(i+1));
                }
  
                changed = 0;
@@@ -901,7 -902,7 +902,7 @@@ int cmd_clean(int argc, const char **ar
        struct pathspec pathspec;
        struct strbuf buf = STRBUF_INIT;
        struct string_list exclude_list = STRING_LIST_INIT_NODUP;
 -      struct exclude_list *el;
 +      struct pattern_list *pl;
        struct string_list_item *item;
        const char *qname;
        struct option options[] = {
  
        if (force > 1)
                rm_flags = 0;
+       else
+               dir.flags |= DIR_SKIP_NESTED_GIT;
  
        dir.flags |= DIR_SHOW_OTHER_DIRECTORIES;
  
+       if (argc) {
+               /*
+                * Remaining args implies pathspecs specified, and we should
+                * recurse within those.
+                */
+               remove_directories = 1;
+       }
        if (remove_directories)
                dir.flags |= DIR_SHOW_IGNORED_TOO | DIR_KEEP_UNTRACKED_CONTENTS;
  
        if (!ignored)
                setup_standard_excludes(&dir);
  
 -      el = add_exclude_list(&dir, EXC_CMDL, "--exclude option");
 +      pl = add_pattern_list(&dir, EXC_CMDL, "--exclude option");
        for (i = 0; i < exclude_list.nr; i++)
 -              add_exclude(exclude_list.items[i].string, "", 0, el, -(i+1));
 +              add_pattern(exclude_list.items[i].string, "", 0, pl, -(i+1));
  
        parse_pathspec(&pathspec, 0,
                       PATHSPEC_PREFER_CWD,
        for_each_string_list_item(item, &del_list) {
                struct stat st;
  
+               strbuf_reset(&abs_path);
                if (prefix)
                        strbuf_addstr(&abs_path, prefix);
  
                                printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
                        }
                }
-               strbuf_reset(&abs_path);
        }
  
        strbuf_release(&abs_path);
diff --combined dir.c
index cab9c2a4588669f3aef3637ada377712998fa01a,bd39b86be4fa8c95df0cfc4e1c7d5b604b167d55..61f559f98008afe61e748fb1821fb45e0bec913f
--- 1/dir.c
--- 2/dir.c
+++ b/dir.c
@@@ -139,7 -139,7 +139,7 @@@ static size_t common_prefix_len(const s
         * ":(icase)path" is treated as a pathspec full of
         * wildcard. In other words, only prefix is considered common
         * prefix. If the pathspec is abc/foo abc/bar, running in
-        * subdir xyz, the common prefix is still xyz, not xuz/abc as
+        * subdir xyz, the common prefix is still xyz, not xyz/abc as
         * in non-:(icase).
         */
        GUARD_PATHSPEC(pathspec,
@@@ -273,19 -273,30 +273,30 @@@ static int do_read_blob(const struct ob
  
  #define DO_MATCH_EXCLUDE   (1<<0)
  #define DO_MATCH_DIRECTORY (1<<1)
- #define DO_MATCH_SUBMODULE (1<<2)
+ #define DO_MATCH_LEADING_PATHSPEC (1<<2)
  
  /*
-  * Does 'match' match the given name?
-  * A match is found if
+  * Does the given pathspec match the given name?  A match is found if
   *
-  * (1) the 'match' string is leading directory of 'name', or
-  * (2) the 'match' string is a wildcard and matches 'name', or
-  * (3) the 'match' string is exactly the same as 'name'.
+  * (1) the pathspec string is leading directory of 'name' ("RECURSIVELY"), or
+  * (2) the pathspec string has a leading part matching 'name' ("LEADING"), or
+  * (3) the pathspec string is a wildcard and matches 'name' ("WILDCARD"), or
+  * (4) the pathspec string is exactly the same as 'name' ("EXACT").
   *
-  * and the return value tells which case it was.
+  * Return value tells which case it was (1-4), or 0 when there is no match.
   *
-  * It returns 0 when there is no match.
+  * It may be instructive to look at a small table of concrete examples
+  * to understand the differences between 1, 2, and 4:
+  *
+  *                              Pathspecs
+  *                |    a/b    |   a/b/    |   a/b/c
+  *          ------+-----------+-----------+------------
+  *          a/b   |  EXACT    |  EXACT[1] | LEADING[2]
+  *  Names   a/b/  | RECURSIVE |   EXACT   | LEADING[2]
+  *          a/b/c | RECURSIVE | RECURSIVE |   EXACT
+  *
+  * [1] Only if DO_MATCH_DIRECTORY is passed; otherwise, this is NOT a match.
+  * [2] Only if DO_MATCH_LEADING_PATHSPEC is passed; otherwise, not a match.
   */
  static int match_pathspec_item(const struct index_state *istate,
                               const struct pathspec_item *item, int prefix,
                         item->nowildcard_len - prefix))
                return MATCHED_FNMATCH;
  
-       /* Perform checks to see if "name" is a super set of the pathspec */
-       if (flags & DO_MATCH_SUBMODULE) {
+       /* Perform checks to see if "name" is a leading string of the pathspec */
+       if (flags & DO_MATCH_LEADING_PATHSPEC) {
                /* name is a literal prefix of the pathspec */
+               int offset = name[namelen-1] == '/' ? 1 : 0;
                if ((namelen < matchlen) &&
-                   (match[namelen] == '/') &&
+                   (match[namelen-offset] == '/') &&
                    !ps_strncmp(item, match, name, namelen))
-                       return MATCHED_RECURSIVELY;
+                       return MATCHED_RECURSIVELY_LEADING_PATHSPEC;
  
                /* name" doesn't match up to the first wild character */
                if (item->nowildcard_len < item->len &&
                 * The submodules themselves will be able to perform more
                 * accurate matching to determine if the pathspec matches.
                 */
-               return MATCHED_RECURSIVELY;
+               return MATCHED_RECURSIVELY_LEADING_PATHSPEC;
        }
  
        return 0;
@@@ -497,7 -509,7 +509,7 @@@ int submodule_path_match(const struct i
                                        strlen(submodule_name),
                                        0, seen,
                                        DO_MATCH_DIRECTORY |
-                                       DO_MATCH_SUBMODULE);
+                                       DO_MATCH_LEADING_PATHSPEC);
        return matched;
  }
  
@@@ -561,7 -573,7 +573,7 @@@ int no_wildcard(const char *string
        return string[simple_length(string)] == '\0';
  }
  
 -void parse_exclude_pattern(const char **pattern,
 +void parse_path_pattern(const char **pattern,
                           int *patternlen,
                           unsigned *flags,
                           int *nowildcardlen)
  
        *flags = 0;
        if (*p == '!') {
 -              *flags |= EXC_FLAG_NEGATIVE;
 +              *flags |= PATTERN_FLAG_NEGATIVE;
                p++;
        }
        len = strlen(p);
        if (len && p[len - 1] == '/') {
                len--;
 -              *flags |= EXC_FLAG_MUSTBEDIR;
 +              *flags |= PATTERN_FLAG_MUSTBEDIR;
        }
        for (i = 0; i < len; i++) {
                if (p[i] == '/')
                        break;
        }
        if (i == len)
 -              *flags |= EXC_FLAG_NODIR;
 +              *flags |= PATTERN_FLAG_NODIR;
        *nowildcardlen = simple_length(p);
        /*
         * we should have excluded the trailing slash from 'p' too,
        if (*nowildcardlen > len)
                *nowildcardlen = len;
        if (*p == '*' && no_wildcard(p + 1))
 -              *flags |= EXC_FLAG_ENDSWITH;
 +              *flags |= PATTERN_FLAG_ENDSWITH;
        *pattern = p;
        *patternlen = len;
  }
  
 -void add_exclude(const char *string, const char *base,
 -               int baselen, struct exclude_list *el, int srcpos)
 +void add_pattern(const char *string, const char *base,
 +               int baselen, struct pattern_list *pl, int srcpos)
  {
 -      struct exclude *x;
 +      struct path_pattern *pattern;
        int patternlen;
        unsigned flags;
        int nowildcardlen;
  
 -      parse_exclude_pattern(&string, &patternlen, &flags, &nowildcardlen);
 -      if (flags & EXC_FLAG_MUSTBEDIR) {
 -              FLEXPTR_ALLOC_MEM(x, pattern, string, patternlen);
 +      parse_path_pattern(&string, &patternlen, &flags, &nowildcardlen);
 +      if (flags & PATTERN_FLAG_MUSTBEDIR) {
 +              FLEXPTR_ALLOC_MEM(pattern, pattern, string, patternlen);
        } else {
 -              x = xmalloc(sizeof(*x));
 -              x->pattern = string;
 +              pattern = xmalloc(sizeof(*pattern));
 +              pattern->pattern = string;
        }
 -      x->patternlen = patternlen;
 -      x->nowildcardlen = nowildcardlen;
 -      x->base = base;
 -      x->baselen = baselen;
 -      x->flags = flags;
 -      x->srcpos = srcpos;
 -      ALLOC_GROW(el->excludes, el->nr + 1, el->alloc);
 -      el->excludes[el->nr++] = x;
 -      x->el = el;
 +      pattern->patternlen = patternlen;
 +      pattern->nowildcardlen = nowildcardlen;
 +      pattern->base = base;
 +      pattern->baselen = baselen;
 +      pattern->flags = flags;
 +      pattern->srcpos = srcpos;
 +      ALLOC_GROW(pl->patterns, pl->nr + 1, pl->alloc);
 +      pl->patterns[pl->nr++] = pattern;
 +      pattern->pl = pl;
  }
  
  static int read_skip_worktree_file_from_index(const struct index_state *istate,
  }
  
  /*
 - * Frees memory within el which was allocated for exclude patterns and
 - * the file buffer.  Does not free el itself.
 + * Frees memory within pl which was allocated for exclude patterns and
 + * the file buffer.  Does not free pl itself.
   */
 -void clear_exclude_list(struct exclude_list *el)
 +void clear_pattern_list(struct pattern_list *pl)
  {
        int i;
  
 -      for (i = 0; i < el->nr; i++)
 -              free(el->excludes[i]);
 -      free(el->excludes);
 -      free(el->filebuf);
 +      for (i = 0; i < pl->nr; i++)
 +              free(pl->patterns[i]);
 +      free(pl->patterns);
 +      free(pl->filebuf);
  
 -      memset(el, 0, sizeof(*el));
 +      memset(pl, 0, sizeof(*pl));
  }
  
  static void trim_trailing_spaces(char *buf)
@@@ -762,21 -774,21 +774,21 @@@ static void invalidate_directory(struc
                dir->dirs[i]->recurse = 0;
  }
  
 -static int add_excludes_from_buffer(char *buf, size_t size,
 +static int add_patterns_from_buffer(char *buf, size_t size,
                                    const char *base, int baselen,
 -                                  struct exclude_list *el);
 +                                  struct pattern_list *pl);
  
  /*
   * Given a file with name "fname", read it (either from disk, or from
   * an index if 'istate' is non-null), parse it and store the
 - * exclude rules in "el".
 + * exclude rules in "pl".
   *
   * If "ss" is not NULL, compute SHA-1 of the exclude file and fill
 - * stat data from disk (only valid if add_excludes returns zero). If
 + * stat data from disk (only valid if add_patterns returns zero). If
   * ss_valid is non-zero, "ss" must contain good value as input.
   */
 -static int add_excludes(const char *fname, const char *base, int baselen,
 -                      struct exclude_list *el, struct index_state *istate,
 +static int add_patterns(const char *fname, const char *base, int baselen,
 +                      struct pattern_list *pl, struct index_state *istate,
                        struct oid_stat *oid_stat)
  {
        struct stat st;
                }
        }
  
 -      add_excludes_from_buffer(buf, size, base, baselen, el);
 +      add_patterns_from_buffer(buf, size, base, baselen, pl);
        return 0;
  }
  
 -static int add_excludes_from_buffer(char *buf, size_t size,
 +static int add_patterns_from_buffer(char *buf, size_t size,
                                    const char *base, int baselen,
 -                                  struct exclude_list *el)
 +                                  struct pattern_list *pl)
  {
        int i, lineno = 1;
        char *entry;
  
 -      el->filebuf = buf;
 +      pl->filebuf = buf;
  
        if (skip_utf8_bom(&buf, size))
 -              size -= buf - el->filebuf;
 +              size -= buf - pl->filebuf;
  
        entry = buf;
  
                        if (entry != buf + i && entry[0] != '#') {
                                buf[i - (i && buf[i-1] == '\r')] = 0;
                                trim_trailing_spaces(entry);
 -                              add_exclude(entry, base, baselen, el, lineno);
 +                              add_pattern(entry, base, baselen, pl, lineno);
                        }
                        lineno++;
                        entry = buf + i + 1;
        return 0;
  }
  
 -int add_excludes_from_file_to_list(const char *fname, const char *base,
 -                                 int baselen, struct exclude_list *el,
 +int add_patterns_from_file_to_list(const char *fname, const char *base,
 +                                 int baselen, struct pattern_list *pl,
                                   struct index_state *istate)
  {
 -      return add_excludes(fname, base, baselen, el, istate, NULL);
 +      return add_patterns(fname, base, baselen, pl, istate, NULL);
  }
  
 -int add_excludes_from_blob_to_list(
 +int add_patterns_from_blob_to_list(
        struct object_id *oid,
        const char *base, int baselen,
 -      struct exclude_list *el)
 +      struct pattern_list *pl)
  {
        char *buf;
        size_t size;
        if (r != 1)
                return r;
  
 -      add_excludes_from_buffer(buf, size, base, baselen, el);
 +      add_patterns_from_buffer(buf, size, base, baselen, pl);
        return 0;
  }
  
 -struct exclude_list *add_exclude_list(struct dir_struct *dir,
 +struct pattern_list *add_pattern_list(struct dir_struct *dir,
                                      int group_type, const char *src)
  {
 -      struct exclude_list *el;
 +      struct pattern_list *pl;
        struct exclude_list_group *group;
  
        group = &dir->exclude_list_group[group_type];
 -      ALLOC_GROW(group->el, group->nr + 1, group->alloc);
 -      el = &group->el[group->nr++];
 -      memset(el, 0, sizeof(*el));
 -      el->src = src;
 -      return el;
 +      ALLOC_GROW(group->pl, group->nr + 1, group->alloc);
 +      pl = &group->pl[group->nr++];
 +      memset(pl, 0, sizeof(*pl));
 +      pl->src = src;
 +      return pl;
  }
  
  /*
   * Used to set up core.excludesfile and .git/info/exclude lists.
   */
 -static void add_excludes_from_file_1(struct dir_struct *dir, const char *fname,
 +static void add_patterns_from_file_1(struct dir_struct *dir, const char *fname,
                                     struct oid_stat *oid_stat)
  {
 -      struct exclude_list *el;
 +      struct pattern_list *pl;
        /*
         * catch setup_standard_excludes() that's called before
         * dir->untracked is assigned. That function behaves
         */
        if (!dir->untracked)
                dir->unmanaged_exclude_files++;
 -      el = add_exclude_list(dir, EXC_FILE, fname);
 -      if (add_excludes(fname, "", 0, el, NULL, oid_stat) < 0)
 +      pl = add_pattern_list(dir, EXC_FILE, fname);
 +      if (add_patterns(fname, "", 0, pl, NULL, oid_stat) < 0)
                die(_("cannot use %s as an exclude file"), fname);
  }
  
 -void add_excludes_from_file(struct dir_struct *dir, const char *fname)
 +void add_patterns_from_file(struct dir_struct *dir, const char *fname)
  {
        dir->unmanaged_exclude_files++; /* see validate_untracked_cache() */
 -      add_excludes_from_file_1(dir, fname, NULL);
 +      add_patterns_from_file_1(dir, fname, NULL);
  }
  
  int match_basename(const char *basename, int basenamelen,
                if (patternlen == basenamelen &&
                    !fspathncmp(pattern, basename, basenamelen))
                        return 1;
 -      } else if (flags & EXC_FLAG_ENDSWITH) {
 +      } else if (flags & PATTERN_FLAG_ENDSWITH) {
                /* "*literal" matching against "fooliteral" */
                if (patternlen - 1 <= basenamelen &&
                    !fspathncmp(pattern + 1,
@@@ -1021,97 -1033,85 +1033,97 @@@ int match_pathname(const char *pathname
   * any, determines the fate.  Returns the exclude_list element which
   * matched, or NULL for undecided.
   */
 -static struct exclude *last_exclude_matching_from_list(const char *pathname,
 +static struct path_pattern *last_matching_pattern_from_list(const char *pathname,
                                                       int pathlen,
                                                       const char *basename,
                                                       int *dtype,
 -                                                     struct exclude_list *el,
 +                                                     struct pattern_list *pl,
                                                       struct index_state *istate)
  {
 -      struct exclude *exc = NULL; /* undecided */
 +      struct path_pattern *res = NULL; /* undecided */
        int i;
  
 -      if (!el->nr)
 +      if (!pl->nr)
                return NULL;    /* undefined */
  
 -      for (i = el->nr - 1; 0 <= i; i--) {
 -              struct exclude *x = el->excludes[i];
 -              const char *exclude = x->pattern;
 -              int prefix = x->nowildcardlen;
 +      for (i = pl->nr - 1; 0 <= i; i--) {
 +              struct path_pattern *pattern = pl->patterns[i];
 +              const char *exclude = pattern->pattern;
 +              int prefix = pattern->nowildcardlen;
  
 -              if (x->flags & EXC_FLAG_MUSTBEDIR) {
 +              if (pattern->flags & PATTERN_FLAG_MUSTBEDIR) {
                        if (*dtype == DT_UNKNOWN)
                                *dtype = get_dtype(NULL, istate, pathname, pathlen);
                        if (*dtype != DT_DIR)
                                continue;
                }
  
 -              if (x->flags & EXC_FLAG_NODIR) {
 +              if (pattern->flags & PATTERN_FLAG_NODIR) {
                        if (match_basename(basename,
                                           pathlen - (basename - pathname),
 -                                         exclude, prefix, x->patternlen,
 -                                         x->flags)) {
 -                              exc = x;
 +                                         exclude, prefix, pattern->patternlen,
 +                                         pattern->flags)) {
 +                              res = pattern;
                                break;
                        }
                        continue;
                }
  
 -              assert(x->baselen == 0 || x->base[x->baselen - 1] == '/');
 +              assert(pattern->baselen == 0 ||
 +                     pattern->base[pattern->baselen - 1] == '/');
                if (match_pathname(pathname, pathlen,
 -                                 x->base, x->baselen ? x->baselen - 1 : 0,
 -                                 exclude, prefix, x->patternlen, x->flags)) {
 -                      exc = x;
 +                                 pattern->base,
 +                                 pattern->baselen ? pattern->baselen - 1 : 0,
 +                                 exclude, prefix, pattern->patternlen,
 +                                 pattern->flags)) {
 +                      res = pattern;
                        break;
                }
        }
 -      return exc;
 +      return res;
  }
  
  /*
 - * Scan the list and let the last match determine the fate.
 - * Return 1 for exclude, 0 for include and -1 for undecided.
 + * Scan the list of patterns to determine if the ordered list
 + * of patterns matches on 'pathname'.
 + *
 + * Return 1 for a match, 0 for not matched and -1 for undecided.
   */
 -int is_excluded_from_list(const char *pathname,
 -                        int pathlen, const char *basename, int *dtype,
 -                        struct exclude_list *el, struct index_state *istate)
 -{
 -      struct exclude *exclude;
 -      exclude = last_exclude_matching_from_list(pathname, pathlen, basename,
 -                                                dtype, el, istate);
 -      if (exclude)
 -              return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
 -      return -1; /* undecided */
 +enum pattern_match_result path_matches_pattern_list(
 +                              const char *pathname, int pathlen,
 +                              const char *basename, int *dtype,
 +                              struct pattern_list *pl,
 +                              struct index_state *istate)
 +{
 +      struct path_pattern *pattern;
 +      pattern = last_matching_pattern_from_list(pathname, pathlen, basename,
 +                                                dtype, pl, istate);
 +      if (pattern) {
 +              if (pattern->flags & PATTERN_FLAG_NEGATIVE)
 +                      return NOT_MATCHED;
 +              else
 +                      return MATCHED;
 +      }
 +
 +      return UNDECIDED;
  }
  
 -static struct exclude *last_exclude_matching_from_lists(struct dir_struct *dir,
 -                                                      struct index_state *istate,
 -              const char *pathname, int pathlen, const char *basename,
 -              int *dtype_p)
 +static struct path_pattern *last_matching_pattern_from_lists(
 +              struct dir_struct *dir, struct index_state *istate,
 +              const char *pathname, int pathlen,
 +              const char *basename, int *dtype_p)
  {
        int i, j;
        struct exclude_list_group *group;
 -      struct exclude *exclude;
 +      struct path_pattern *pattern;
        for (i = EXC_CMDL; i <= EXC_FILE; i++) {
                group = &dir->exclude_list_group[i];
                for (j = group->nr - 1; j >= 0; j--) {
 -                      exclude = last_exclude_matching_from_list(
 +                      pattern = last_matching_pattern_from_list(
                                pathname, pathlen, basename, dtype_p,
 -                              &group->el[j], istate);
 -                      if (exclude)
 -                              return exclude;
 +                              &group->pl[j], istate);
 +                      if (pattern)
 +                              return pattern;
                }
        }
        return NULL;
@@@ -1126,7 -1126,7 +1138,7 @@@ static void prep_exclude(struct dir_str
                         const char *base, int baselen)
  {
        struct exclude_list_group *group;
 -      struct exclude_list *el;
 +      struct pattern_list *pl;
        struct exclude_stack *stk = NULL;
        struct untracked_cache_dir *untracked;
        int current;
                if (stk->baselen <= baselen &&
                    !strncmp(dir->basebuf.buf, base, stk->baselen))
                        break;
 -              el = &group->el[dir->exclude_stack->exclude_ix];
 +              pl = &group->pl[dir->exclude_stack->exclude_ix];
                dir->exclude_stack = stk->prev;
 -              dir->exclude = NULL;
 -              free((char *)el->src); /* see strbuf_detach() below */
 -              clear_exclude_list(el);
 +              dir->pattern = NULL;
 +              free((char *)pl->src); /* see strbuf_detach() below */
 +              clear_pattern_list(pl);
                free(stk);
                group->nr--;
        }
  
        /* Skip traversing into sub directories if the parent is excluded */
 -      if (dir->exclude)
 +      if (dir->pattern)
                return;
  
        /*
                stk->baselen = cp - base;
                stk->exclude_ix = group->nr;
                stk->ucd = untracked;
 -              el = add_exclude_list(dir, EXC_DIRS, NULL);
 +              pl = add_pattern_list(dir, EXC_DIRS, NULL);
                strbuf_add(&dir->basebuf, base + current, stk->baselen - current);
                assert(stk->baselen == dir->basebuf.len);
  
                if (stk->baselen) {
                        int dt = DT_DIR;
                        dir->basebuf.buf[stk->baselen - 1] = 0;
 -                      dir->exclude = last_exclude_matching_from_lists(dir,
 +                      dir->pattern = last_matching_pattern_from_lists(dir,
                                                                        istate,
                                dir->basebuf.buf, stk->baselen - 1,
                                dir->basebuf.buf + current, &dt);
                        dir->basebuf.buf[stk->baselen - 1] = '/';
 -                      if (dir->exclude &&
 -                          dir->exclude->flags & EXC_FLAG_NEGATIVE)
 -                              dir->exclude = NULL;
 -                      if (dir->exclude) {
 +                      if (dir->pattern &&
 +                          dir->pattern->flags & PATTERN_FLAG_NEGATIVE)
 +                              dir->pattern = NULL;
 +                      if (dir->pattern) {
                                dir->exclude_stack = stk;
                                return;
                        }
                        /*
                         * dir->basebuf gets reused by the traversal, but we
                         * need fname to remain unchanged to ensure the src
 -                       * member of each struct exclude correctly
 +                       * member of each struct path_pattern correctly
                         * back-references its source file.  Other invocations
 -                       * of add_exclude_list provide stable strings, so we
 +                       * of add_pattern_list provide stable strings, so we
                         * strbuf_detach() and free() here in the caller.
                         */
                        struct strbuf sb = STRBUF_INIT;
                        strbuf_addbuf(&sb, &dir->basebuf);
                        strbuf_addstr(&sb, dir->exclude_per_dir);
 -                      el->src = strbuf_detach(&sb, NULL);
 -                      add_excludes(el->src, el->src, stk->baselen, el, istate,
 +                      pl->src = strbuf_detach(&sb, NULL);
 +                      add_patterns(pl->src, pl->src, stk->baselen, pl, istate,
                                     untracked ? &oid_stat : NULL);
                }
                /*
                 * NEEDSWORK: when untracked cache is enabled, prep_exclude()
                 * will first be called in valid_cached_dir() then maybe many
 -               * times more in last_exclude_matching(). When the cache is
 -               * used, last_exclude_matching() will not be called and
 +               * times more in last_matching_pattern(). When the cache is
 +               * used, last_matching_pattern() will not be called and
                 * reading .gitignore content will be a waste.
                 *
                 * So when it's called by valid_cached_dir() and we can get
                 * .gitignore SHA-1 from the index (i.e. .gitignore is not
                 * modified on work tree), we could delay reading the
                 * .gitignore content until we absolutely need it in
 -               * last_exclude_matching(). Be careful about ignore rule
 +               * last_matching_pattern(). Be careful about ignore rule
                 * order, though, if you do that.
                 */
                if (untracked &&
   * Returns the exclude_list element which matched, or NULL for
   * undecided.
   */
 -struct exclude *last_exclude_matching(struct dir_struct *dir,
 +struct path_pattern *last_matching_pattern(struct dir_struct *dir,
                                      struct index_state *istate,
                                      const char *pathname,
                                      int *dtype_p)
  
        prep_exclude(dir, istate, pathname, basename-pathname);
  
 -      if (dir->exclude)
 -              return dir->exclude;
 +      if (dir->pattern)
 +              return dir->pattern;
  
 -      return last_exclude_matching_from_lists(dir, istate, pathname, pathlen,
 +      return last_matching_pattern_from_lists(dir, istate, pathname, pathlen,
                        basename, dtype_p);
  }
  
  int is_excluded(struct dir_struct *dir, struct index_state *istate,
                const char *pathname, int *dtype_p)
  {
 -      struct exclude *exclude =
 -              last_exclude_matching(dir, istate, pathname, dtype_p);
 -      if (exclude)
 -              return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
 +      struct path_pattern *pattern =
 +              last_matching_pattern(dir, istate, pathname, dtype_p);
 +      if (pattern)
 +              return pattern->flags & PATTERN_FLAG_NEGATIVE ? 0 : 1;
        return 0;
  }
  
@@@ -1451,6 -1451,16 +1463,16 @@@ static enum path_treatment treat_direct
                return path_none;
  
        case index_nonexistent:
+               if (dir->flags & DIR_SKIP_NESTED_GIT) {
+                       int nested_repo;
+                       struct strbuf sb = STRBUF_INIT;
+                       strbuf_addstr(&sb, dirname);
+                       nested_repo = is_nonbare_repository_dir(&sb);
+                       strbuf_release(&sb);
+                       if (nested_repo)
+                               return path_none;
+               }
                if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
                        break;
                if (exclude &&
@@@ -1820,7 -1830,7 +1842,7 @@@ static int valid_cached_dir(struct dir_
  
        /*
         * prep_exclude will be called eventually on this directory,
 -       * but it's called much later in last_exclude_matching(). We
 +       * but it's called much later in last_matching_pattern(). We
         * need it now to determine the validity of the cache for this
         * path. The next calls will be nearly no-op, the way
         * prep_exclude() is designed.
@@@ -1950,8 -1960,11 +1972,11 @@@ static enum path_treatment read_directo
                /* recurse into subdir if instructed by treat_path */
                if ((state == path_recurse) ||
                        ((state == path_untracked) &&
-                        (dir->flags & DIR_SHOW_IGNORED_TOO) &&
-                        (get_dtype(cdir.de, istate, path.buf, path.len) == DT_DIR))) {
+                        (get_dtype(cdir.de, istate, path.buf, path.len) == DT_DIR) &&
+                        ((dir->flags & DIR_SHOW_IGNORED_TOO) ||
+                         (pathspec &&
+                          do_match_pathspec(istate, pathspec, path.buf, path.len,
+                                            baselen, NULL, DO_MATCH_LEADING_PATHSPEC) == MATCHED_RECURSIVELY_LEADING_PATHSPEC)))) {
                        struct untracked_cache_dir *ud;
                        ud = lookup_untracked(dir->untracked, untracked,
                                              path.buf + baselen,
                                                         check_only, stop_at_first_file, pathspec);
                        if (subdir_state > dir_state)
                                dir_state = subdir_state;
+                       if (pathspec &&
+                           !match_pathspec(istate, pathspec, path.buf, path.len,
+                                           0 /* prefix */, NULL,
+                                           0 /* do NOT special case dirs */))
+                               state = path_none;
                }
  
                if (check_only) {
@@@ -2500,14 -2519,14 +2531,14 @@@ void setup_standard_excludes(struct dir
        if (!excludes_file)
                excludes_file = xdg_config_home("ignore");
        if (excludes_file && !access_or_warn(excludes_file, R_OK, 0))
 -              add_excludes_from_file_1(dir, excludes_file,
 +              add_patterns_from_file_1(dir, excludes_file,
                                         dir->untracked ? &dir->ss_excludes_file : NULL);
  
        /* per repository user preference */
        if (startup_info->have_repository) {
                const char *path = git_path_info_exclude();
                if (!access_or_warn(path, R_OK, 0))
 -                      add_excludes_from_file_1(dir, path,
 +                      add_patterns_from_file_1(dir, path,
                                                 dir->untracked ? &dir->ss_info_exclude : NULL);
        }
  }
@@@ -2539,18 -2558,18 +2570,18 @@@ void clear_directory(struct dir_struct 
  {
        int i, j;
        struct exclude_list_group *group;
 -      struct exclude_list *el;
 +      struct pattern_list *pl;
        struct exclude_stack *stk;
  
        for (i = EXC_CMDL; i <= EXC_FILE; i++) {
                group = &dir->exclude_list_group[i];
                for (j = 0; j < group->nr; j++) {
 -                      el = &group->el[j];
 +                      pl = &group->pl[j];
                        if (i == EXC_DIRS)
 -                              free((char *)el->src);
 -                      clear_exclude_list(el);
 +                              free((char *)pl->src);
 +                      clear_pattern_list(pl);
                }
 -              free(group->el);
 +              free(group->pl);
        }
  
        stk = dir->exclude_stack;
diff --combined dir.h
index 608696c95831008bed2898d243e000a4d266086f,739aea7c9686f5b37f09c29f2ee6b1373dc1d0de..2fbdef014f57c6e95a032dfba8fe8864b5a7d6dc
--- 1/dir.h
--- 2/dir.h
+++ b/dir.h
@@@ -11,24 -11,24 +11,24 @@@ struct dir_entry 
        char name[FLEX_ARRAY]; /* more */
  };
  
 -#define EXC_FLAG_NODIR 1
 -#define EXC_FLAG_ENDSWITH 4
 -#define EXC_FLAG_MUSTBEDIR 8
 -#define EXC_FLAG_NEGATIVE 16
 +#define PATTERN_FLAG_NODIR 1
 +#define PATTERN_FLAG_ENDSWITH 4
 +#define PATTERN_FLAG_MUSTBEDIR 8
 +#define PATTERN_FLAG_NEGATIVE 16
  
 -struct exclude {
 +struct path_pattern {
        /*
 -       * This allows callers of last_exclude_matching() etc.
 +       * This allows callers of last_matching_pattern() etc.
         * to determine the origin of the matching pattern.
         */
 -      struct exclude_list *el;
 +      struct pattern_list *pl;
  
        const char *pattern;
        int patternlen;
        int nowildcardlen;
        const char *base;
        int baselen;
 -      unsigned flags;         /* EXC_FLAG_* */
 +      unsigned flags;         /* PATTERN_FLAG_* */
  
        /*
         * Counting starts from 1 for line numbers in ignore files,
@@@ -44,7 -44,7 +44,7 @@@
   * can also be used to represent the list of --exclude values passed
   * via CLI args.
   */
 -struct exclude_list {
 +struct pattern_list {
        int nr;
        int alloc;
  
@@@ -54,7 -54,7 +54,7 @@@
        /* origin of list, e.g. path to filename, or descriptive string */
        const char *src;
  
 -      struct exclude **excludes;
 +      struct path_pattern **patterns;
  };
  
  /*
@@@ -72,7 -72,7 +72,7 @@@ struct exclude_stack 
  
  struct exclude_list_group {
        int nr, alloc;
 -      struct exclude_list *el;
 +      struct pattern_list *pl;
  };
  
  struct oid_stat {
@@@ -156,7 -156,8 +156,8 @@@ struct dir_struct 
                DIR_SHOW_IGNORED_TOO = 1<<5,
                DIR_COLLECT_KILLED_ONLY = 1<<6,
                DIR_KEEP_UNTRACKED_CONTENTS = 1<<7,
-               DIR_SHOW_IGNORED_TOO_MODE_MATCHING = 1<<8
+               DIR_SHOW_IGNORED_TOO_MODE_MATCHING = 1<<8,
+               DIR_SKIP_NESTED_GIT = 1<<9
        } flags;
        struct dir_entry **entries;
        struct dir_entry **ignored;
         * matching exclude struct if the directory is excluded.
         */
        struct exclude_stack *exclude_stack;
 -      struct exclude *exclude;
 +      struct path_pattern *pattern;
        struct strbuf basebuf;
  
        /* Enable untracked file cache if set */
@@@ -211,8 -212,9 +212,9 @@@ int count_slashes(const char *s)
   * when populating the seen[] array.
   */
  #define MATCHED_RECURSIVELY 1
- #define MATCHED_FNMATCH 2
- #define MATCHED_EXACTLY 3
+ #define MATCHED_RECURSIVELY_LEADING_PATHSPEC 2
+ #define MATCHED_FNMATCH 3
+ #define MATCHED_EXACTLY 4
  int simple_length(const char *match);
  int no_wildcard(const char *string);
  char *common_prefix(const struct pathspec *pathspec);
@@@ -230,23 -232,10 +232,23 @@@ int read_directory(struct dir_struct *
                   const char *path, int len,
                   const struct pathspec *pathspec);
  
 -int is_excluded_from_list(const char *pathname, int pathlen,
 -                        const char *basename, int *dtype,
 -                        struct exclude_list *el,
 -                        struct index_state *istate);
 +enum pattern_match_result {
 +      UNDECIDED = -1,
 +      NOT_MATCHED = 0,
 +      MATCHED = 1,
 +};
 +
 +/*
 + * Scan the list of patterns to determine if the ordered list
 + * of patterns matches on 'pathname'.
 + *
 + * Return 1 for a match, 0 for not matched and -1 for undecided.
 + */
 +enum pattern_match_result path_matches_pattern_list(const char *pathname,
 +                              int pathlen,
 +                              const char *basename, int *dtype,
 +                              struct pattern_list *pl,
 +                              struct index_state *istate);
  struct dir_entry *dir_add_ignored(struct dir_struct *dir,
                                  struct index_state *istate,
                                  const char *pathname, int len);
@@@ -261,26 -250,26 +263,26 @@@ int match_pathname(const char *, int
                   const char *, int,
                   const char *, int, int, unsigned);
  
 -struct exclude *last_exclude_matching(struct dir_struct *dir,
 -                                    struct index_state *istate,
 -                                    const char *name, int *dtype);
 +struct path_pattern *last_matching_pattern(struct dir_struct *dir,
 +                                         struct index_state *istate,
 +                                         const char *name, int *dtype);
  
  int is_excluded(struct dir_struct *dir,
                struct index_state *istate,
                const char *name, int *dtype);
  
 -struct exclude_list *add_exclude_list(struct dir_struct *dir,
 +struct pattern_list *add_pattern_list(struct dir_struct *dir,
                                      int group_type, const char *src);
 -int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen,
 -                                 struct exclude_list *el, struct  index_state *istate);
 -void add_excludes_from_file(struct dir_struct *, const char *fname);
 -int add_excludes_from_blob_to_list(struct object_id *oid,
 +int add_patterns_from_file_to_list(const char *fname, const char *base, int baselen,
 +                                 struct pattern_list *pl, struct  index_state *istate);
 +void add_patterns_from_file(struct dir_struct *, const char *fname);
 +int add_patterns_from_blob_to_list(struct object_id *oid,
                                   const char *base, int baselen,
 -                                 struct exclude_list *el);
 -void parse_exclude_pattern(const char **string, int *patternlen, unsigned *flags, int *nowildcardlen);
 -void add_exclude(const char *string, const char *base,
 -               int baselen, struct exclude_list *el, int srcpos);
 -void clear_exclude_list(struct exclude_list *el);
 +                                 struct pattern_list *pl);
 +void parse_path_pattern(const char **string, int *patternlen, unsigned *flags, int *nowildcardlen);
 +void add_pattern(const char *string, const char *base,
 +               int baselen, struct pattern_list *pl, int srcpos);
 +void clear_pattern_list(struct pattern_list *pl);
  void clear_directory(struct dir_struct *dir);
  
  int repo_file_exists(struct repository *repo, const char *path);