Merge branch 'jc/directory-attrs-regression-fix'
authorJunio C Hamano <gitster@pobox.com>
Wed, 3 Apr 2013 16:34:04 +0000 (09:34 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 3 Apr 2013 16:34:09 +0000 (09:34 -0700)
Fix 1.8.1.x regression that stopped matching "dir" (without
trailing slash) to a directory "dir".

* jc/directory-attrs-regression-fix:
t: check that a pattern without trailing slash matches a directory
dir.c::match_pathname(): pay attention to the length of string parameters
dir.c::match_pathname(): adjust patternlen when shifting pattern
dir.c::match_basename(): pay attention to the length of string parameters
attr.c::path_matches(): special case paths that end with a slash
attr.c::path_matches(): the basename is part of the pathname

1  2 
attr.c
dir.c
diff --combined attr.c
index e2f9377891a7f6e0d56803016726e3cd3137fdc2,4d620bc52a1e18ab3cf1f93c74551f38e484f472..689bc2a8961fac72a01e615764af53f49d01c3ec
--- 1/attr.c
--- 2/attr.c
+++ b/attr.c
@@@ -255,11 -255,9 +255,11 @@@ static struct match_attr *parse_attr_li
                                      &res->u.pat.patternlen,
                                      &res->u.pat.flags,
                                      &res->u.pat.nowildcardlen);
 -              if (res->u.pat.flags & EXC_FLAG_NEGATIVE)
 -                      die(_("Negative patterns are forbidden in git attributes\n"
 -                            "Use '\\!' for literal leading exclamation."));
 +              if (res->u.pat.flags & EXC_FLAG_NEGATIVE) {
 +                      warning(_("Negative patterns are ignored in git attributes\n"
 +                                "Use '\\!' for literal leading exclamation."));
 +                      return NULL;
 +              }
        }
        res->is_macro = is_macro;
        res->num_attr = num_attr;
   * (reading the file from top to bottom), .gitattribute of the root
   * directory (again, reading the file from top to bottom) down to the
   * current directory, and then scan the list backwards to find the first match.
 - * This is exactly the same as what excluded() does in dir.c to deal with
 + * This is exactly the same as what is_excluded() does in dir.c to deal with
   * .gitignore
   */
  
@@@ -657,24 -655,24 +657,24 @@@ static void prepare_attr_stack(const ch
  }
  
  static int path_matches(const char *pathname, int pathlen,
-                       const char *basename,
+                       int basename_offset,
                        const struct pattern *pat,
                        const char *base, int baselen)
  {
        const char *pattern = pat->pattern;
        int prefix = pat->nowildcardlen;
+       int isdir = (pathlen && pathname[pathlen - 1] == '/');
  
-       if ((pat->flags & EXC_FLAG_MUSTBEDIR) &&
-           ((!pathlen) || (pathname[pathlen-1] != '/')))
+       if ((pat->flags & EXC_FLAG_MUSTBEDIR) && !isdir)
                return 0;
  
        if (pat->flags & EXC_FLAG_NODIR) {
-               return match_basename(basename,
-                                     pathlen - (basename - pathname),
+               return match_basename(pathname + basename_offset,
+                                     pathlen - basename_offset - isdir,
                                      pattern, prefix,
                                      pat->patternlen, pat->flags);
        }
-       return match_pathname(pathname, pathlen,
+       return match_pathname(pathname, pathlen - isdir,
                              base, baselen,
                              pattern, prefix, pat->patternlen, pat->flags);
  }
@@@ -693,7 -691,7 +693,7 @@@ static int fill_one(const char *what, s
  
                if (*n == ATTR__UNKNOWN) {
                        debug_set(what,
 -                                a->is_macro ? a->u.attr->name : a->u.pattern,
 +                                a->is_macro ? a->u.attr->name : a->u.pat.pattern,
                                  attr, v);
                        *n = v;
                        rem--;
        return rem;
  }
  
- static int fill(const char *path, int pathlen, const char *basename,
+ static int fill(const char *path, int pathlen, int basename_offset,
                struct attr_stack *stk, int rem)
  {
        int i;
                struct match_attr *a = stk->attrs[i];
                if (a->is_macro)
                        continue;
-               if (path_matches(path, pathlen, basename,
+               if (path_matches(path, pathlen, basename_offset,
                                 &a->u.pat, base, stk->originlen))
                        rem = fill_one("fill", a, rem);
        }
@@@ -752,7 -750,8 +752,8 @@@ static void collect_all_attrs(const cha
  {
        struct attr_stack *stk;
        int i, pathlen, rem, dirlen;
-       const char *basename, *cp, *last_slash = NULL;
+       const char *cp, *last_slash = NULL;
+       int basename_offset;
  
        for (cp = path; *cp; cp++) {
                if (*cp == '/' && cp[1])
        }
        pathlen = cp - path;
        if (last_slash) {
-               basename = last_slash + 1;
+               basename_offset = last_slash + 1 - path;
                dirlen = last_slash - path;
        } else {
-               basename = path;
+               basename_offset = 0;
                dirlen = 0;
        }
  
  
        rem = attr_nr;
        for (stk = attr_stack; 0 < rem && stk; stk = stk->prev)
-               rem = fill(path, pathlen, basename, stk, rem);
+               rem = fill(path, pathlen, basename_offset, stk, rem);
  }
  
  int git_check_attr(const char *path, int num, struct git_attr_check *check)
diff --combined dir.c
index 57394e452eb0de117b27f64804e529b617a6c7e0,8fea94e1853eecd18dc6d5eb36dd090bdcb3e246..1e42b2b1509e2cf154a1d9722138fbeae437daf5
--- 1/dir.c
--- 2/dir.c
+++ b/dir.c
@@@ -2,15 -2,12 +2,15 @@@
   * This handles recursive filename detection with exclude
   * files, index knowledge etc..
   *
 + * See Documentation/technical/api-directory-listing.txt
 + *
   * Copyright (C) Linus Torvalds, 2005-2006
   *             Junio Hamano, 2005-2006
   */
  #include "cache.h"
  #include "dir.h"
  #include "refs.h"
 +#include "wildmatch.h"
  
  struct path_simplify {
        int len;
@@@ -37,33 -34,37 +37,62 @@@ int fnmatch_icase(const char *pattern, 
        return fnmatch(pattern, string, flags | (ignore_case ? FNM_CASEFOLD : 0));
  }
  
 -      match_status = fnmatch_icase(use_pat, use_str, flags);
 +inline int git_fnmatch(const char *pattern, const char *string,
 +                     int flags, int prefix)
 +{
 +      int fnm_flags = 0;
 +      if (flags & GFNM_PATHNAME)
 +              fnm_flags |= FNM_PATHNAME;
 +      if (prefix > 0) {
 +              if (strncmp(pattern, string, prefix))
 +                      return FNM_NOMATCH;
 +              pattern += prefix;
 +              string += prefix;
 +      }
 +      if (flags & GFNM_ONESTAR) {
 +              int pattern_len = strlen(++pattern);
 +              int string_len = strlen(string);
 +              return string_len < pattern_len ||
 +                     strcmp(pattern,
 +                            string + string_len - pattern_len);
 +      }
 +      return fnmatch(pattern, string, fnm_flags);
 +}
 +
+ static int fnmatch_icase_mem(const char *pattern, int patternlen,
+                            const char *string, int stringlen,
+                            int flags)
+ {
+       int match_status;
+       struct strbuf pat_buf = STRBUF_INIT;
+       struct strbuf str_buf = STRBUF_INIT;
+       const char *use_pat = pattern;
+       const char *use_str = string;
+       if (pattern[patternlen]) {
+               strbuf_add(&pat_buf, pattern, patternlen);
+               use_pat = pat_buf.buf;
+       }
+       if (string[stringlen]) {
+               strbuf_add(&str_buf, string, stringlen);
+               use_str = str_buf.buf;
+       }
++      if (ignore_case)
++              flags |= WM_CASEFOLD;
++      match_status = wildmatch(use_pat, use_str, flags, NULL);
+       strbuf_release(&pat_buf);
+       strbuf_release(&str_buf);
+       return match_status;
+ }
  static size_t common_prefix_len(const char **pathspec)
  {
        const char *n, *first;
        size_t max = 0;
 +      int literal = limit_pathspec_to_literal();
  
        if (!pathspec)
                return max;
                size_t i, len = 0;
                for (i = 0; first == n || i < max; i++) {
                        char c = n[i];
 -                      if (!c || c != first[i] || is_glob_special(c))
 +                      if (!c || c != first[i] || (!literal && is_glob_special(c)))
                                break;
                        if (c == '/')
                                len = i + 1;
@@@ -143,7 -144,6 +172,7 @@@ int within_depth(const char *name, int 
  static int match_one(const char *match, const char *name, int namelen)
  {
        int matchlen;
 +      int literal = limit_pathspec_to_literal();
  
        /* If the match was just the prefix, we matched */
        if (!*match)
                for (;;) {
                        unsigned char c1 = tolower(*match);
                        unsigned char c2 = tolower(*name);
 -                      if (c1 == '\0' || is_glob_special(c1))
 +                      if (c1 == '\0' || (!literal && is_glob_special(c1)))
                                break;
                        if (c1 != c2)
                                return 0;
                for (;;) {
                        unsigned char c1 = *match;
                        unsigned char c2 = *name;
 -                      if (c1 == '\0' || is_glob_special(c1))
 +                      if (c1 == '\0' || (!literal && is_glob_special(c1)))
                                break;
                        if (c1 != c2)
                                return 0;
                }
        }
  
 -
        /*
         * If we don't match the matchstring exactly,
         * we need to match by fnmatch
         */
        matchlen = strlen(match);
 -      if (strncmp_icase(match, name, matchlen))
 +      if (strncmp_icase(match, name, matchlen)) {
 +              if (literal)
 +                      return 0;
                return !fnmatch_icase(match, name, 0) ? MATCHED_FNMATCH : 0;
 +      }
  
        if (namelen == matchlen)
                return MATCHED_EXACTLY;
  }
  
  /*
 - * Given a name and a list of pathspecs, see if the name matches
 - * any of the pathspecs.  The caller is also interested in seeing
 - * all pathspec matches some names it calls this function with
 - * (otherwise the user could have mistyped the unmatched pathspec),
 - * and a mark is left in seen[] array for pathspec element that
 - * actually matched anything.
 + * Given a name and a list of pathspecs, returns the nature of the
 + * closest (i.e. most specific) match of the name to any of the
 + * pathspecs.
 + *
 + * The caller typically calls this multiple times with the same
 + * pathspec and seen[] array but with different name/namelen
 + * (e.g. entries from the index) and is interested in seeing if and
 + * how each pathspec matches all the names it calls this function
 + * with.  A mark is left in the seen[] array for each pathspec element
 + * indicating the closest type of match that element achieved, so if
 + * seen[n] remains zero after multiple invocations, that means the nth
 + * pathspec did not match any names, which could indicate that the
 + * user mistyped the nth pathspec.
   */
  int match_pathspec(const char **pathspec, const char *name, int namelen,
                int prefix, char *seen)
@@@ -266,29 -257,19 +295,29 @@@ static int match_pathspec_item(const st
                        return MATCHED_RECURSIVELY;
        }
  
 -      if (item->use_wildcard && !fnmatch(match, name, 0))
 +      if (item->nowildcard_len < item->len &&
 +          !git_fnmatch(match, name,
 +                       item->flags & PATHSPEC_ONESTAR ? GFNM_ONESTAR : 0,
 +                       item->nowildcard_len - prefix))
                return MATCHED_FNMATCH;
  
        return 0;
  }
  
  /*
 - * Given a name and a list of pathspecs, see if the name matches
 - * any of the pathspecs.  The caller is also interested in seeing
 - * all pathspec matches some names it calls this function with
 - * (otherwise the user could have mistyped the unmatched pathspec),
 - * and a mark is left in seen[] array for pathspec element that
 - * actually matched anything.
 + * Given a name and a list of pathspecs, returns the nature of the
 + * closest (i.e. most specific) match of the name to any of the
 + * pathspecs.
 + *
 + * The caller typically calls this multiple times with the same
 + * pathspec and seen[] array but with different name/namelen
 + * (e.g. entries from the index) and is interested in seeing if and
 + * how each pathspec matches all the names it calls this function
 + * with.  A mark is left in the seen[] array for each pathspec element
 + * indicating the closest type of match that element achieved, so if
 + * seen[n] remains zero after multiple invocations, that means the nth
 + * pathspec did not match any names, which could indicate that the
 + * user mistyped the nth pathspec.
   */
  int match_pathspec_depth(const struct pathspec *ps,
                         const char *name, int namelen,
@@@ -393,7 -374,7 +422,7 @@@ void parse_exclude_pattern(const char *
  }
  
  void add_exclude(const char *string, const char *base,
 -               int baselen, struct exclude_list *which)
 +               int baselen, struct exclude_list *el, int srcpos)
  {
        struct exclude *x;
        int patternlen;
        x->base = base;
        x->baselen = baselen;
        x->flags = flags;
 -      ALLOC_GROW(which->excludes, which->nr + 1, which->alloc);
 -      which->excludes[which->nr++] = x;
 +      x->srcpos = srcpos;
 +      ALLOC_GROW(el->excludes, el->nr + 1, el->alloc);
 +      el->excludes[el->nr++] = x;
 +      x->el = el;
  }
  
  static void *read_skip_worktree_file_from_index(const char *path, size_t *size)
        return data;
  }
  
 -void free_excludes(struct exclude_list *el)
 +/*
 + * Frees memory within el which was allocated for exclude patterns and
 + * the file buffer.  Does not free el itself.
 + */
 +void clear_exclude_list(struct exclude_list *el)
  {
        int i;
  
        for (i = 0; i < el->nr; i++)
                free(el->excludes[i]);
        free(el->excludes);
 +      free(el->filebuf);
  
        el->nr = 0;
        el->excludes = NULL;
 +      el->filebuf = NULL;
  }
  
  int add_excludes_from_file_to_list(const char *fname,
                                   const char *base,
                                   int baselen,
 -                                 char **buf_p,
 -                                 struct exclude_list *which,
 +                                 struct exclude_list *el,
                                   int check_index)
  {
        struct stat st;
 -      int fd, i;
 +      int fd, i, lineno = 1;
        size_t size = 0;
        char *buf, *entry;
  
                close(fd);
        }
  
 -      if (buf_p)
 -              *buf_p = buf;
 +      el->filebuf = buf;
        entry = buf;
        for (i = 0; i < size; i++) {
                if (buf[i] == '\n') {
                        if (entry != buf + i && entry[0] != '#') {
                                buf[i - (i && buf[i-1] == '\r')] = 0;
 -                              add_exclude(entry, base, baselen, which);
 +                              add_exclude(entry, base, baselen, el, lineno);
                        }
 +                      lineno++;
                        entry = buf + i + 1;
                }
        }
        return 0;
  }
  
 +struct exclude_list *add_exclude_list(struct dir_struct *dir,
 +                                    int group_type, const char *src)
 +{
 +      struct exclude_list *el;
 +      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;
 +}
 +
 +/*
 + * Used to set up core.excludesfile and .git/info/exclude lists.
 + */
  void add_excludes_from_file(struct dir_struct *dir, const char *fname)
  {
 -      if (add_excludes_from_file_to_list(fname, "", 0, NULL,
 -                                         &dir->exclude_list[EXC_FILE], 0) < 0)
 +      struct exclude_list *el;
 +      el = add_exclude_list(dir, EXC_FILE, fname);
 +      if (add_excludes_from_file_to_list(fname, "", 0, el, 0) < 0)
                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;
            (baselen + strlen(dir->exclude_per_dir) >= PATH_MAX))
                return; /* too long a path -- ignore */
  
 -      /* Pop the ones that are not the prefix of the path being checked. */
 -      el = &dir->exclude_list[EXC_DIRS];
 +      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;
 -              while (stk->exclude_ix < el->nr)
 -                      free(el->excludes[--el->nr]);
 -              free(stk->filebuf);
 +              free((char *)el->src); /* see strdup() below */
 +              clear_exclude_list(el);
                free(stk);
 +              group->nr--;
        }
  
        /* Read from the parent directories and push them down. */
                }
                stk->prev = dir->exclude_stack;
                stk->baselen = cp - base;
 -              stk->exclude_ix = el->nr;
                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,
 -                                             &stk->filebuf, el, 1);
 +                                             el, 1);
                dir->exclude_stack = stk;
                current = stk->baselen;
        }
@@@ -626,15 -564,20 +655,20 @@@ int match_basename(const char *basename
                   int flags)
  {
        if (prefix == patternlen) {
-               if (!strcmp_icase(pattern, basename))
+               if (patternlen == basenamelen &&
+                   !strncmp_icase(pattern, basename, basenamelen))
                        return 1;
        } else if (flags & EXC_FLAG_ENDSWITH) {
+               /* "*literal" matching against "fooliteral" */
                if (patternlen - 1 <= basenamelen &&
-                   !strcmp_icase(pattern + 1,
-                                 basename + basenamelen - patternlen + 1))
+                   !strncmp_icase(pattern + 1,
+                                  basename + basenamelen - (patternlen - 1),
+                                  patternlen - 1))
                        return 1;
        } else {
-               if (fnmatch_icase(pattern, basename, 0) == 0)
+               if (fnmatch_icase_mem(pattern, patternlen,
+                                     basename, basenamelen,
+                                     0) == 0)
                        return 1;
        }
        return 0;
@@@ -654,6 -597,7 +688,7 @@@ int match_pathname(const char *pathname
         */
        if (*pattern == '/') {
                pattern++;
+               patternlen--;
                prefix--;
        }
  
                if (strncmp_icase(pattern, name, prefix))
                        return 0;
                pattern += prefix;
+               patternlen -= prefix;
                name    += prefix;
                namelen -= prefix;
+               /*
+                * If the whole pattern did not have a wildcard,
+                * then our prefix match is all we need; we
+                * do not need to call fnmatch at all.
+                */
+               if (!patternlen && !namelen)
+                       return 1;
        }
  
-       return wildmatch(pattern, name,
-                        WM_PATHNAME | (ignore_case ? WM_CASEFOLD : 0),
-                        NULL) == 0;
+       return fnmatch_icase_mem(pattern, patternlen,
+                                name, namelen,
 -                               FNM_PATHNAME) == 0;
++                               WM_PATHNAME) == 0;
  }
  
 -/* Scan the list and let the last match determine the fate.
 - * Return 1 for exclude, 0 for include and -1 for undecided.
 +/*
 + * Scan the given exclude list in reverse to see whether pathname
 + * should be ignored.  The first match (i.e. the last on the list), if
 + * any, determines the fate.  Returns the exclude_list element which
 + * matched, or NULL for undecided.
   */
 -int excluded_from_list(const char *pathname,
 -                     int pathlen, const char *basename, int *dtype,
 -                     struct exclude_list *el)
 +static struct exclude *last_exclude_matching_from_list(const char *pathname,
 +                                                     int pathlen,
 +                                                     const char *basename,
 +                                                     int *dtype,
 +                                                     struct exclude_list *el)
  {
        int i;
  
        if (!el->nr)
 -              return -1;      /* undefined */
 +              return NULL;    /* undefined */
  
        for (i = el->nr - 1; 0 <= i; i--) {
                struct exclude *x = el->excludes[i];
                const char *exclude = x->pattern;
 -              int to_exclude = x->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
                int prefix = x->nowildcardlen;
  
                if (x->flags & EXC_FLAG_MUSTBEDIR) {
                                           pathlen - (basename - pathname),
                                           exclude, prefix, x->patternlen,
                                           x->flags))
 -                              return to_exclude;
 +                              return x;
                        continue;
                }
  
                if (match_pathname(pathname, pathlen,
                                   x->base, x->baselen ? x->baselen - 1 : 0,
                                   exclude, prefix, x->patternlen, x->flags))
 -                      return to_exclude;
 +                      return x;
        }
 +      return NULL; /* undecided */
 +}
 +
 +/*
 + * Scan the list and let the last match determine the fate.
 + * Return 1 for exclude, 0 for include and -1 for undecided.
 + */
 +int is_excluded_from_list(const char *pathname,
 +                        int pathlen, const char *basename, int *dtype,
 +                        struct exclude_list *el)
 +{
 +      struct exclude *exclude;
 +      exclude = last_exclude_matching_from_list(pathname, pathlen, basename, dtype, el);
 +      if (exclude)
 +              return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
        return -1; /* undecided */
  }
  
 -static int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
 +/*
 + * 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)
  {
        int pathlen = strlen(pathname);
 -      int st;
 +      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 (st = EXC_CMDL; st <= EXC_FILE; st++) {
 -              switch (excluded_from_list(pathname, pathlen, basename,
 -                                         dtype_p, &dir->exclude_list[st])) {
 -              case 0:
 -                      return 0;
 -              case 1:
 -                      return 1;
 +
 +      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(
 +                              pathname, pathlen, basename, dtype_p,
 +                              &group->el[j]);
 +                      if (exclude)
 +                              return exclude;
                }
        }
 +      return NULL;
 +}
 +
 +/*
 + * 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.
 + */
 +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;
  }
  
@@@ -801,7 -709,6 +845,7 @@@ void path_exclude_check_init(struct pat
                             struct dir_struct *dir)
  {
        check->dir = dir;
 +      check->exclude = NULL;
        strbuf_init(&check->path, 256);
  }
  
@@@ -811,41 -718,32 +855,41 @@@ void path_exclude_check_clear(struct pa
  }
  
  /*
 - * 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.
 + * 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.
   */
 -int path_excluded(struct path_exclude_check *check,
 -                const char *name, int namelen, int *dtype)
 +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;
  
        /*
         * we allow the caller to pass namelen as an optimization; it
         * must match the length of the name, as we eventually call
 -       * excluded() on the whole name string.
 +       * is_excluded() on the whole name string.
         */
        if (namelen < 0)
                namelen = strlen(name);
  
 +      /*
 +       * 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 1;
 +              return check->exclude;
  
        strbuf_setlen(path, 0);
        for (i = 0; name[i]; i++) {
  
                if (ch == '/') {
                        int dt = DT_DIR;
 -                      if (excluded(check->dir, path->buf, &dt))
 -                              return 1;
 +                      exclude = last_exclude_matching(check->dir,
 +                                                      path->buf, &dt);
 +                      if (exclude) {
 +                              check->exclude = exclude;
 +                              return exclude;
 +                      }
                }
                strbuf_addch(path, ch);
        }
        /* An entry in the index; cannot be a directory with subentries */
        strbuf_setlen(path, 0);
  
 -      return excluded(check->dir, name, dtype);
 +      return last_exclude_matching(check->dir, name, dtype);
 +}
 +
 +/*
 + * 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.
 + */
 +int is_path_excluded(struct path_exclude_check *check,
 +                const char *name, int namelen, int *dtype)
 +{
 +      struct exclude *exclude =
 +              last_exclude_matching_path(check, name, namelen, dtype);
 +      if (exclude)
 +              return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
 +      return 0;
  }
  
  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 (cache_name_exists(pathname, len, ignore_case))
 +      if (!(dir->flags & DIR_SHOW_IGNORED) &&
 +          cache_name_exists(pathname, len, ignore_case))
                return NULL;
  
        ALLOC_GROW(dir->entries, dir->nr+1, dir->alloc);
@@@ -1000,9 -878,8 +1044,9 @@@ static enum exist_status directory_exis
   * traversal routine.
   *
   * Case 1: If we *already* have entries in the index under that
 - * directory name, we always recurse into the directory to see
 - * all the files.
 + * directory name, we recurse into the directory to see all the files,
 + * unless the directory is excluded and we want to show ignored
 + * directories
   *
   * Case 2: If we *already* have that directory name as a gitlink,
   * we always continue to see it as a gitlink, regardless of whether
   *      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.
   *  (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.
@@@ -1031,15 -905,12 +1075,15 @@@ enum directory_treatment 
  };
  
  static enum directory_treatment treat_directory(struct dir_struct *dir,
 -      const char *dirname, int len,
 +      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;
  
        case index_gitdir:
        }
  
        /* This is the "show_other_directories" case */
 -      if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
 +
 +      /*
 +       * 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);
 +      }
 +
 +      return exclude_file;
 +}
 +
  /*
   * This is an inexact early pruning of any recursive directory
   * reading - if the path cannot possibly be in the pathspec,
@@@ -1244,7 -1060,7 +1288,7 @@@ static enum path_treatment treat_one_pa
                                          const struct path_simplify *simplify,
                                          int dtype, struct dirent *de)
  {
 -      int exclude = excluded(dir, path->buf, &dtype);
 +      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);
        if (dtype == DT_UNKNOWN)
                dtype = get_dtype(de, path->buf, path->len);
  
 -      /*
 -       * Do we want to see just the ignored files?
 -       * We still need to recurse into directories,
 -       * even if we don't ignore them, since the
 -       * directory may contain files that we do..
 -       */
 -      if (!exclude && (dir->flags & DIR_SHOW_IGNORED)) {
 -              if (dtype != DT_DIR)
 -                      return path_ignored;
 -      }
 -
        switch (dtype) {
        default:
                return path_ignored;
        case DT_DIR:
                strbuf_addch(path, '/');
 -              switch (treat_directory(dir, path->buf, path->len, simplify)) {
 +
 +              switch (treat_directory(dir, path->buf, path->len, exclude, simplify)) {
                case show_directory:
 -                      if (exclude != !!(dir->flags
 -                                        & DIR_SHOW_IGNORED))
 -                              return path_ignored;
                        break;
                case recurse_into_directory:
                        return path_recurse;
                break;
        case DT_REG:
        case DT_LNK:
 -              break;
 +              switch (treat_file(dir, path, exclude, &dtype)) {
 +              case 1:
 +                      return path_ignored;
 +              default:
 +                      break;
 +              }
        }
        return path_handled;
  }
@@@ -1649,18 -1473,9 +1693,18 @@@ int init_pathspec(struct pathspec *path
  
                item->match = path;
                item->len = strlen(path);
 -              item->use_wildcard = !no_wildcard(path);
 -              if (item->use_wildcard)
 -                      pathspec->has_wildcard = 1;
 +              item->flags = 0;
 +              if (limit_pathspec_to_literal()) {
 +                      item->nowildcard_len = item->len;
 +              } else {
 +                      item->nowildcard_len = simple_length(path);
 +                      if (item->nowildcard_len < item->len) {
 +                              pathspec->has_wildcard = 1;
 +                              if (path[item->nowildcard_len] == '*' &&
 +                                  no_wildcard(path + item->nowildcard_len + 1))
 +                                      item->flags |= PATHSPEC_ONESTAR;
 +                      }
 +              }
        }
  
        qsort(pathspec->items, pathspec->nr,
@@@ -1674,41 -1489,3 +1718,41 @@@ void free_pathspec(struct pathspec *pat
        free(pathspec->items);
        pathspec->items = NULL;
  }
 +
 +int limit_pathspec_to_literal(void)
 +{
 +      static int flag = -1;
 +      if (flag < 0)
 +              flag = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0);
 +      return flag;
 +}
 +
 +/*
 + * Frees memory within dir which was allocated for exclude lists and
 + * the exclude_stack.  Does not free dir itself.
 + */
 +void clear_directory(struct dir_struct *dir)
 +{
 +      int i, j;
 +      struct exclude_list_group *group;
 +      struct exclude_list *el;
 +      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];
 +                      if (i == EXC_DIRS)
 +                              free((char *)el->src);
 +                      clear_exclude_list(el);
 +              }
 +              free(group->el);
 +      }
 +
 +      stk = dir->exclude_stack;
 +      while (stk) {
 +              struct exclude_stack *prev = stk->prev;
 +              free(stk);
 +              stk = prev;
 +      }
 +}