Fix "git diff --stat" for interesting - but empty - file changes
[gitweb.git] / dir.c
diff --git a/dir.c b/dir.c
index 2c02b312b77f3363c234bdc66faaeafd5899586d..486833986ed4b4e7d05d2086d53b15ec63905dd0 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);
@@ -383,6 +397,8 @@ int add_excludes_from_file_to_list(const char *fname,
 
        fd = open(fname, O_RDONLY);
        if (fd < 0 || fstat(fd, &st) < 0) {
+               if (errno != ENOENT)
+                       warn_on_inaccessible(fname);
                if (0 <= fd)
                        close(fd);
                if (!check_index ||
@@ -498,57 +514,69 @@ 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 */
 }
@@ -1055,21 +1083,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;
@@ -1292,12 +1305,17 @@ 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 (!access(path, R_OK))
+       if (!excludes_file) {
+               home_config_paths(NULL, &xdg_path, "ignore");
+               excludes_file = xdg_path;
+       }
+       if (!access_or_warn(path, R_OK))
                add_excludes_from_file(dir, path);
-       if (excludes_file && !access(excludes_file, R_OK))
+       if (excludes_file && !access_or_warn(excludes_file, R_OK))
                add_excludes_from_file(dir, excludes_file);
 }