t9129-git-svn-i18n-commitencoding.sh: use the $( ... ) construct for command substitution
[gitweb.git] / dir.c
diff --git a/dir.c b/dir.c
index 7b25634832716ade46686d118184c4d43d8c11e7..d2a8f06b0243bda92c04f57d96c0b18838ee7dce 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -882,6 +882,25 @@ int match_pathname(const char *pathname, int pathlen,
                 */
                if (!patternlen && !namelen)
                        return 1;
+               /*
+                * This can happen when we ignore some exclude rules
+                * on directories in other to see if negative rules
+                * may match. E.g.
+                *
+                * /abc
+                * !/abc/def/ghi
+                *
+                * The pattern of interest is "/abc". On the first
+                * try, we should match path "abc" with this pattern
+                * in the "if" statement right above, but the caller
+                * ignores it.
+                *
+                * On the second try with paths within "abc",
+                * e.g. "abc/xyz", we come here and try to match it
+                * with "/abc".
+                */
+               if (!patternlen && namelen && *name == '/')
+                       return 1;
        }
 
        return fnmatch_icase_mem(pattern, patternlen,
@@ -889,6 +908,48 @@ int match_pathname(const char *pathname, int pathlen,
                                 WM_PATHNAME) == 0;
 }
 
+/*
+ * Return non-zero if pathname is a directory and an ancestor of the
+ * literal path in a (negative) pattern. This is used to keep
+ * descending in "foo" and "foo/bar" when the pattern is
+ * "!foo/bar/.gitignore". "foo/notbar" will not be descended however.
+ */
+static int match_neg_path(const char *pathname, int pathlen, int *dtype,
+                         const char *base, int baselen,
+                         const char *pattern, int prefix, int patternlen,
+                         int flags)
+{
+       assert((flags & EXC_FLAG_NEGATIVE) && !(flags & EXC_FLAG_NODIR));
+
+       if (*dtype == DT_UNKNOWN)
+               *dtype = get_dtype(NULL, pathname, pathlen);
+       if (*dtype != DT_DIR)
+               return 0;
+
+       if (*pattern == '/') {
+               pattern++;
+               patternlen--;
+               prefix--;
+       }
+
+       if (baselen) {
+               if (((pathlen < baselen && base[pathlen] == '/') ||
+                    pathlen == baselen) &&
+                   !strncmp_icase(pathname, base, pathlen))
+                       return 1;
+               pathname += baselen + 1;
+               pathlen  -= baselen + 1;
+       }
+
+
+       if (prefix &&
+           ((pathlen < prefix && pattern[pathlen] == '/') &&
+            !strncmp_icase(pathname, pattern, pathlen)))
+               return 1;
+
+       return 0;
+}
+
 /*
  * 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
@@ -901,7 +962,8 @@ static struct exclude *last_exclude_matching_from_list(const char *pathname,
                                                       int *dtype,
                                                       struct exclude_list *el)
 {
-       int i;
+       struct exclude *exc = NULL; /* undecided */
+       int i, matched_negative_path = 0;
 
        if (!el->nr)
                return NULL;    /* undefined */
@@ -922,18 +984,33 @@ static struct exclude *last_exclude_matching_from_list(const char *pathname,
                        if (match_basename(basename,
                                           pathlen - (basename - pathname),
                                           exclude, prefix, x->patternlen,
-                                          x->flags))
-                               return x;
+                                          x->flags)) {
+                               exc = x;
+                               break;
+                       }
                        continue;
                }
 
                assert(x->baselen == 0 || x->base[x->baselen - 1] == '/');
                if (match_pathname(pathname, pathlen,
                                   x->base, x->baselen ? x->baselen - 1 : 0,
+                                  exclude, prefix, x->patternlen, x->flags)) {
+                       exc = x;
+                       break;
+               }
+
+               if ((x->flags & EXC_FLAG_NEGATIVE) && !matched_negative_path &&
+                   match_neg_path(pathname, pathlen, dtype, x->base,
+                                  x->baselen ? x->baselen - 1 : 0,
                                   exclude, prefix, x->patternlen, x->flags))
-                       return x;
+                       matched_negative_path = 1;
        }
-       return NULL; /* undecided */
+       if (exc &&
+           !(exc->flags & EXC_FLAG_NEGATIVE) &&
+           !(exc->flags & EXC_FLAG_NODIR) &&
+           matched_negative_path)
+               exc = NULL;
+       return exc;
 }
 
 /*
@@ -1202,29 +1279,15 @@ enum exist_status {
  */
 static enum exist_status directory_exists_in_index_icase(const char *dirname, int len)
 {
-       const struct cache_entry *ce = cache_dir_exists(dirname, len);
-       unsigned char endchar;
-
-       if (!ce)
-               return index_nonexistent;
-       endchar = ce->name[len];
+       struct cache_entry *ce;
 
-       /*
-        * The cache_entry structure returned will contain this dirname
-        * and possibly additional path components.
-        */
-       if (endchar == '/')
+       if (cache_dir_exists(dirname, len))
                return index_directory;
 
-       /*
-        * If there are no additional path components, then this cache_entry
-        * represents a submodule.  Submodules, despite being directories,
-        * are stored in the cache without a closing slash.
-        */
-       if (!endchar && S_ISGITLINK(ce->ce_mode))
+       ce = cache_file_exists(dirname, len, ignore_case);
+       if (ce && S_ISGITLINK(ce->ce_mode))
                return index_gitdir;
 
-       /* This should never be hit, but it exists just in case. */
        return index_nonexistent;
 }
 
@@ -1519,8 +1582,7 @@ static enum path_treatment treat_path_fast(struct dir_struct *dir,
        }
        strbuf_addstr(path, cdir->ucd->name);
        /* treat_one_path() does this before it calls treat_directory() */
-       if (path->buf[path->len - 1] != '/')
-               strbuf_addch(path, '/');
+       strbuf_complete(path, '/');
        if (cdir->ucd->check_only)
                /*
                 * check_only is set as a result of treat_directory() getting
@@ -2030,6 +2092,15 @@ int file_exists(const char *f)
        return lstat(f, &sb) == 0;
 }
 
+static int cmp_icase(char a, char b)
+{
+       if (a == b)
+               return 0;
+       if (ignore_case)
+               return toupper(a) - toupper(b);
+       return a - b;
+}
+
 /*
  * Given two normalized paths (a trailing slash is ok), if subdir is
  * outside dir, return -1.  Otherwise return the offset in subdir that
@@ -2041,7 +2112,7 @@ int dir_inside_of(const char *subdir, const char *dir)
 
        assert(dir && subdir && *dir && *subdir);
 
-       while (*dir && *subdir && *dir == *subdir) {
+       while (*dir && *subdir && !cmp_icase(*dir, *subdir)) {
                dir++;
                subdir++;
                offset++;
@@ -2126,8 +2197,7 @@ static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)
                else
                        return -1;
        }
-       if (path->buf[original_len - 1] != '/')
-               strbuf_addch(path, '/');
+       strbuf_complete(path, '/');
 
        len = path->len;
        while ((e = readdir(dir)) != NULL) {