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

attr.c
dir.c
t/t5002-archive-attr-pattern.sh
diff --git a/attr.c b/attr.c
index d181d98b0e12959bff2074720c77daea661429dd..23be4abf44d0b00d0fc1943f4038a1fc85028f70 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -657,24 +657,24 @@ static void prepare_attr_stack(const char *path, int dirlen)
 }
 
 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);
 }
@@ -703,7 +703,7 @@ static int fill_one(const char *what, struct match_attr *a, int 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;
@@ -713,7 +713,7 @@ static int fill(const char *path, int pathlen, const char *basename,
                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 +752,8 @@ static void collect_all_attrs(const char *path)
 {
        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])
@@ -760,10 +761,10 @@ static void collect_all_attrs(const char *path)
        }
        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;
        }
 
@@ -773,7 +774,7 @@ static void collect_all_attrs(const char *path)
 
        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 --git a/dir.c b/dir.c
index a473ca23fba1272483bd8041e1bf901b8e12c32e..6fdd3b27165cc9ce7e56cabc25bf6cb6904c23b2 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -34,6 +34,33 @@ int fnmatch_icase(const char *pattern, const char *string, int flags)
        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 @@ int match_basename(const char *basename, int basenamelen,
                   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 @@ int match_pathname(const char *pathname, int pathlen,
         */
        if (*pattern == '/') {
                pattern++;
+               patternlen--;
                prefix--;
        }
 
@@ -591,11 +624,22 @@ int match_pathname(const char *pathname, int pathlen,
                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.
index 0c847fb45482b67266dc6eb9ec62b8cc7b5a9d76..6667d159ab0950f10a3271f1cb81a63953214f17 100755 (executable)
@@ -27,6 +27,25 @@ test_expect_success 'setup' '
        echo ignored-only-if-dir/ export-ignore >>.git/info/attributes &&
        git add ignored-only-if-dir &&
 
+       mkdir -p ignored-without-slash &&
+       echo "ignored without slash" >ignored-without-slash/foo &&
+       git add ignored-without-slash/foo &&
+       echo "ignored-without-slash export-ignore" >>.git/info/attributes &&
+
+       mkdir -p wildcard-without-slash &&
+       echo "ignored without slash" >wildcard-without-slash/foo &&
+       git add wildcard-without-slash/foo &&
+       echo "wild*-without-slash export-ignore" >>.git/info/attributes &&
+
+       mkdir -p deep/and/slashless &&
+       echo "ignored without slash" >deep/and/slashless/foo &&
+       git add deep/and/slashless/foo &&
+       echo "deep/and/slashless export-ignore" >>.git/info/attributes &&
+
+       mkdir -p deep/with/wildcard &&
+       echo "ignored without slash" >deep/with/wildcard/foo &&
+       git add deep/with/wildcard/foo &&
+       echo "deep/*t*/wildcard export-ignore" >>.git/info/attributes &&
 
        mkdir -p one-level-lower/two-levels-lower/ignored-only-if-dir &&
        echo ignored by ignored dir >one-level-lower/two-levels-lower/ignored-only-if-dir/ignored-by-ignored-dir &&
@@ -49,6 +68,14 @@ test_expect_exists   archive/not-ignored-dir/ignored-only-if-dir
 test_expect_exists     archive/not-ignored-dir/
 test_expect_missing    archive/ignored-only-if-dir/
 test_expect_missing    archive/ignored-ony-if-dir/ignored-by-ignored-dir
+test_expect_missing    archive/ignored-without-slash/ &&
+test_expect_missing    archive/ignored-without-slash/foo &&
+test_expect_missing    archive/wildcard-without-slash/
+test_expect_missing    archive/wildcard-without-slash/foo &&
+test_expect_missing    archive/deep/and/slashless/ &&
+test_expect_missing    archive/deep/and/slashless/foo &&
+test_expect_missing    archive/deep/with/wildcard/ &&
+test_expect_missing    archive/deep/with/wildcard/foo &&
 test_expect_exists     archive/one-level-lower/
 test_expect_missing    archive/one-level-lower/two-levels-lower/ignored-only-if-dir/
 test_expect_missing    archive/one-level-lower/two-levels-lower/ignored-ony-if-dir/ignored-by-ignored-dir