Merge branch 'jk/maint-null-in-trees'
[gitweb.git] / dir.c
diff --git a/dir.c b/dir.c
index ed1510fbc808f4c9799eb84a3c38030b7565f3bc..240bf0c49c9654b7cef56959786613e17113c3a0 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -288,9 +288,24 @@ int match_pathspec_depth(const struct pathspec *ps,
        return retval;
 }
 
+/*
+ * Return the length of the "simple" part of a path match limiter.
+ */
+static int simple_length(const char *match)
+{
+       int len = -1;
+
+       for (;;) {
+               unsigned char c = *match++;
+               len++;
+               if (c == '\0' || is_glob_special(c))
+                       return len;
+       }
+}
+
 static int no_wildcard(const char *string)
 {
-       return string[strcspn(string, "*?[{\\")] == '\0';
+       return string[simple_length(string)] == '\0';
 }
 
 void add_exclude(const char *string, const char *base,
@@ -326,8 +341,7 @@ void add_exclude(const char *string, const char *base,
        x->flags = flags;
        if (!strchr(string, '/'))
                x->flags |= EXC_FLAG_NODIR;
-       if (no_wildcard(string))
-               x->flags |= EXC_FLAG_NOWILDCARD;
+       x->nowildcardlen = simple_length(string);
        if (*string == '*' && no_wildcard(string+1))
                x->flags |= EXC_FLAG_ENDSWITH;
        ALLOC_GROW(which->excludes, which->nr + 1, which->alloc);
@@ -498,62 +512,74 @@ int excluded_from_list(const char *pathname,
 {
        int i;
 
-       if (el->nr) {
-               for (i = el->nr - 1; 0 <= i; i--) {
-                       struct exclude *x = el->excludes[i];
-                       const char *exclude = x->pattern;
-                       int to_exclude = x->to_exclude;
-
-                       if (x->flags & EXC_FLAG_MUSTBEDIR) {
-                               if (*dtype == DT_UNKNOWN)
-                                       *dtype = get_dtype(NULL, pathname, pathlen);
-                               if (*dtype != DT_DIR)
-                                       continue;
-                       }
+       if (!el->nr)
+               return -1;      /* undefined */
 
-                       if (x->flags & EXC_FLAG_NODIR) {
-                               /* match basename */
-                               if (x->flags & EXC_FLAG_NOWILDCARD) {
-                                       if (!strcmp_icase(exclude, basename))
-                                               return to_exclude;
-                               } else if (x->flags & EXC_FLAG_ENDSWITH) {
-                                       if (x->patternlen - 1 <= pathlen &&
-                                           !strcmp_icase(exclude + 1, pathname + pathlen - x->patternlen + 1))
-                                               return to_exclude;
-                               } else {
-                                       if (fnmatch_icase(exclude, basename, 0) == 0)
-                                               return to_exclude;
-                               }
-                       }
-                       else {
-                               /* match with FNM_PATHNAME:
-                                * exclude has base (baselen long) implicitly
-                                * in front of it.
-                                */
-                               int baselen = x->baselen;
-                               if (*exclude == '/')
-                                       exclude++;
-
-                               if (pathlen < baselen ||
-                                   (baselen && pathname[baselen-1] != '/') ||
-                                   strncmp_icase(pathname, x->base, baselen))
-                                   continue;
-
-                               if (x->flags & EXC_FLAG_NOWILDCARD) {
-                                       if (!strcmp_icase(exclude, pathname + baselen))
-                                               return to_exclude;
-                               } else {
-                                       if (fnmatch_icase(exclude, pathname+baselen,
-                                                   FNM_PATHNAME) == 0)
-                                           return to_exclude;
-                               }
+       for (i = el->nr - 1; 0 <= i; i--) {
+               struct exclude *x = el->excludes[i];
+               const char *name, *exclude = x->pattern;
+               int to_exclude = x->to_exclude;
+               int namelen, prefix = x->nowildcardlen;
+
+               if (x->flags & EXC_FLAG_MUSTBEDIR) {
+                       if (*dtype == DT_UNKNOWN)
+                               *dtype = get_dtype(NULL, pathname, pathlen);
+                       if (*dtype != DT_DIR)
+                               continue;
+               }
+
+               if (x->flags & EXC_FLAG_NODIR) {
+                       /* match basename */
+                       if (prefix == x->patternlen) {
+                               if (!strcmp_icase(exclude, basename))
+                                       return to_exclude;
+                       } else if (x->flags & EXC_FLAG_ENDSWITH) {
+                               if (x->patternlen - 1 <= pathlen &&
+                                   !strcmp_icase(exclude + 1, pathname + pathlen - x->patternlen + 1))
+                                       return to_exclude;
+                       } else {
+                               if (fnmatch_icase(exclude, basename, 0) == 0)
+                                       return to_exclude;
                        }
+                       continue;
+               }
+
+               /* match with FNM_PATHNAME:
+                * exclude has base (baselen long) implicitly in front of it.
+                */
+               if (*exclude == '/') {
+                       exclude++;
+                       prefix--;
+               }
+
+               if (pathlen < x->baselen ||
+                   (x->baselen && pathname[x->baselen-1] != '/') ||
+                   strncmp_icase(pathname, x->base, x->baselen))
+                       continue;
+
+               namelen = x->baselen ? pathlen - x->baselen : pathlen;
+               name = pathname + pathlen  - namelen;
+
+               /* if the non-wildcard part is longer than the
+                  remaining pathname, surely it cannot match */
+               if (prefix > namelen)
+                       continue;
+
+               if (prefix) {
+                       if (strncmp_icase(exclude, name, prefix))
+                               continue;
+                       exclude += prefix;
+                       name    += prefix;
+                       namelen -= prefix;
                }
+
+               if (!namelen || !fnmatch_icase(exclude, name, FNM_PATHNAME))
+                       return to_exclude;
        }
        return -1; /* undecided */
 }
 
-int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
+static int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
 {
        int pathlen = strlen(pathname);
        int st;
@@ -573,6 +599,64 @@ int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
        return 0;
 }
 
+void path_exclude_check_init(struct path_exclude_check *check,
+                            struct dir_struct *dir)
+{
+       check->dir = dir;
+       strbuf_init(&check->path, 256);
+}
+
+void path_exclude_check_clear(struct path_exclude_check *check)
+{
+       strbuf_release(&check->path);
+}
+
+/*
+ * 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.
+ *
+ * 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)
+{
+       int i;
+       struct strbuf *path = &check->path;
+
+       /*
+        * 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.
+        */
+       if (namelen < 0)
+               namelen = strlen(name);
+
+       if (path->len &&
+           path->len <= namelen &&
+           !memcmp(name, path->buf, path->len) &&
+           (!name[path->len] || name[path->len] == '/'))
+               return 1;
+
+       strbuf_setlen(path, 0);
+       for (i = 0; name[i]; i++) {
+               int ch = name[i];
+
+               if (ch == '/') {
+                       int dt = DT_DIR;
+                       if (excluded(check->dir, path->buf, &dt))
+                               return 1;
+               }
+               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);
+}
+
 static struct dir_entry *dir_entry_new(const char *pathname, int len)
 {
        struct dir_entry *ent;
@@ -997,21 +1081,6 @@ static int cmp_name(const void *p1, const void *p2)
                                  e2->name, e2->len);
 }
 
-/*
- * Return the length of the "simple" part of a path match limiter.
- */
-static int simple_length(const char *match)
-{
-       int len = -1;
-
-       for (;;) {
-               unsigned char c = *match++;
-               len++;
-               if (c == '\0' || is_glob_special(c))
-                       return len;
-       }
-}
-
 static struct path_simplify *create_simplify(const char **pathspec)
 {
        int nr, alloc = 0;
@@ -1234,9 +1303,14 @@ int remove_dir_recursively(struct strbuf *path, int flag)
 void setup_standard_excludes(struct dir_struct *dir)
 {
        const char *path;
+       char *xdg_path;
 
        dir->exclude_per_dir = ".gitignore";
        path = git_path("info/exclude");
+       if (!excludes_file) {
+               home_config_paths(NULL, &xdg_path, "ignore");
+               excludes_file = xdg_path;
+       }
        if (!access(path, R_OK))
                add_excludes_from_file(dir, path);
        if (excludes_file && !access(excludes_file, R_OK))