dir.c: make 'git-status --ignored' work within leading directories
[gitweb.git] / dir.c
diff --git a/dir.c b/dir.c
index 57394e452eb0de117b27f64804e529b617a6c7e0..fd4aeae3e7aeeceda50b16f6707ac77d12d96ffb 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -59,6 +59,35 @@ inline int git_fnmatch(const char *pattern, const char *string,
        return fnmatch(pattern, string, fnm_flags);
 }
 
+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;
+       }
+
+       if (ignore_case)
+               flags |= WM_CASEFOLD;
+       match_status = wildmatch(use_pat, use_str, flags, NULL);
+
+       strbuf_release(&pat_buf);
+       strbuf_release(&str_buf);
+
+       return match_status;
+}
+
 static size_t common_prefix_len(const char **pathspec)
 {
        const char *n, *first;
@@ -626,15 +655,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;
@@ -654,6 +688,7 @@ int match_pathname(const char *pathname, int pathlen,
         */
        if (*pattern == '/') {
                pattern++;
+               patternlen--;
                prefix--;
        }
 
@@ -680,13 +715,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 wildmatch(pattern, name,
-                        WM_PATHNAME | (ignore_case ? WM_CASEFOLD : 0),
-                        NULL) == 0;
+       return fnmatch_icase_mem(pattern, patternlen,
+                                name, namelen,
+                                WM_PATHNAME) == 0;
 }
 
 /*
@@ -1060,22 +1104,30 @@ static enum directory_treatment treat_directory(struct dir_struct *dir,
 
        /* This is the "show_other_directories" case */
 
+       /* might be a sub directory in an excluded directory */
+       if (!exclude) {
+               struct path_exclude_check check;
+               int dt = DT_DIR;
+               path_exclude_check_init(&check, dir);
+               exclude = is_path_excluded(&check, dirname, len, &dt);
+               path_exclude_check_clear(&check);
+       }
+
        /*
         * We are looking for ignored files and our directory is not ignored,
-        * check if it contains only ignored files
+        * check if it contains untracked files (i.e. is listed as untracked)
         */
        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 (ignored)
+                       return ignore_directory;
        }
-       if (!(dir->flags & DIR_SHOW_IGNORED) &&
-           !(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
+
+       if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
                return show_directory;
        if (!read_directory_recursive(dir, dirname, len, 1, simplify))
                return ignore_directory;
@@ -1100,16 +1152,13 @@ static int treat_file(struct dir_struct *dir, struct strbuf *path, int exclude,
        struct path_exclude_check check;
        int exclude_file = 0;
 
+       /* Always exclude indexed files */
+       if (index_name_exists(&the_index, path->buf, path->len, ignore_case))
+               return 1;
+
        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 (!is_path_excluded(&check, path->buf, path->len, dtype))
@@ -1264,7 +1313,6 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
                return path_ignored;
        case DT_DIR:
                strbuf_addch(path, '/');
-
                switch (treat_directory(dir, path->buf, path->len, exclude, simplify)) {
                case show_directory:
                        break;
@@ -1334,8 +1382,7 @@ static int read_directory_recursive(struct dir_struct *dir,
                switch (treat_path(dir, de, &path, baselen, simplify)) {
                case path_recurse:
                        contents += read_directory_recursive(dir, path.buf,
-                                                            path.len, 0,
-                                                            simplify);
+                               path.len, check_only, simplify);
                        continue;
                case path_ignored:
                        continue;
@@ -1400,12 +1447,14 @@ static int treat_leading_path(struct dir_struct *dir,
        struct strbuf sb = STRBUF_INIT;
        int baselen, rc = 0;
        const char *cp;
+       int old_flags = dir->flags;
 
        while (len && path[len - 1] == '/')
                len--;
        if (!len)
                return 1;
        baselen = 0;
+       dir->flags &= ~DIR_SHOW_OTHER_DIRECTORIES;
        while (1) {
                cp = path + baselen + !!baselen;
                cp = memchr(cp, '/', path + len - cp);
@@ -1428,6 +1477,7 @@ static int treat_leading_path(struct dir_struct *dir,
                }
        }
        strbuf_release(&sb);
+       dir->flags = old_flags;
        return rc;
 }
 
@@ -1603,7 +1653,7 @@ int remove_path(const char *name)
 {
        char *slash;
 
-       if (unlink(name) && errno != ENOENT)
+       if (unlink(name) && errno != ENOENT && errno != ENOTDIR)
                return -1;
 
        slash = strrchr(name, '/');