Merge branch 'jc/directory-attrs-regression-fix' into maint-1.8.1
authorJunio C Hamano <gitster@pobox.com>
Sun, 7 Apr 2013 15:45:03 +0000 (08:45 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 7 Apr 2013 15:45:03 +0000 (08:45 -0700)
A pattern "dir" (without trailing slash) in the attributes file
stopped matching a directory "dir" by mistake with an earlier change
that wanted to allow pattern "dir/" to also match.

* 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 d181d98b0e12959bff2074720c77daea661429dd,4d620bc52a1e18ab3cf1f93c74551f38e484f472..23be4abf44d0b00d0fc1943f4038a1fc85028f70
--- 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;
@@@ -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 a473ca23fba1272483bd8041e1bf901b8e12c32e,8fea94e1853eecd18dc6d5eb36dd090bdcb3e246..6fdd3b27165cc9ce7e56cabc25bf6cb6904c23b2
--- 1/dir.c
--- 2/dir.c
+++ b/dir.c
@@@ -34,6 -34,33 +34,33 @@@ int fnmatch_icase(const char *pattern, 
        return fnmatch(pattern, string, flags | (ignore_case ? FNM_CASEFOLD : 0));
  }
  
+ 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;
+       }
+       match_status = fnmatch_icase(use_pat, use_str, flags);
+       strbuf_release(&pat_buf);
+       strbuf_release(&str_buf);
+       return match_status;
+ }
  static size_t common_prefix_len(const char **pathspec)
  {
        const char *n, *first;
@@@ -537,15 -564,20 +564,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;
@@@ -565,6 -597,7 +597,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 fnmatch_icase(pattern, name, FNM_PATHNAME) == 0;
+       return fnmatch_icase_mem(pattern, patternlen,
+                                name, namelen,
+                                FNM_PATHNAME) == 0;
  }
  
  /* Scan the list and let the last match determine the fate.
@@@ -732,8 -776,7 +776,8 @@@ static struct dir_entry *dir_entry_new(
  
  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);
@@@ -835,9 -878,8 +879,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.
@@@ -866,15 -905,12 +910,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 (!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,
@@@ -1094,14 -1075,27 +1138,14 @@@ static enum path_treatment treat_one_pa
        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;
  }