Git.pm: allow command_close_bidi_pipe to be called as method
[gitweb.git] / dir.c
diff --git a/dir.c b/dir.c
index 37807550475cf488b60187872f698cbc6171dcb1..57394e452eb0de117b27f64804e529b617a6c7e0 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -2,6 +2,8 @@
  * This handles recursive filename detection with exclude
  * files, index knowledge etc..
  *
+ * See Documentation/technical/api-directory-listing.txt
+ *
  * Copyright (C) Linus Torvalds, 2005-2006
  *              Junio Hamano, 2005-2006
  */
@@ -192,12 +194,19 @@ static int match_one(const char *match, const char *name, int namelen)
 }
 
 /*
- * Given a name and a list of pathspecs, see if the name matches
- * any of the pathspecs.  The caller is also interested in seeing
- * all pathspec matches some names it calls this function with
- * (otherwise the user could have mistyped the unmatched pathspec),
- * and a mark is left in seen[] array for pathspec element that
- * actually matched anything.
+ * Given a name and a list of pathspecs, returns the nature of the
+ * closest (i.e. most specific) match of the name to any of the
+ * pathspecs.
+ *
+ * The caller typically calls this multiple times with the same
+ * pathspec and seen[] array but with different name/namelen
+ * (e.g. entries from the index) and is interested in seeing if and
+ * how each pathspec matches all the names it calls this function
+ * with.  A mark is left in the seen[] array for each pathspec element
+ * indicating the closest type of match that element achieved, so if
+ * seen[n] remains zero after multiple invocations, that means the nth
+ * pathspec did not match any names, which could indicate that the
+ * user mistyped the nth pathspec.
  */
 int match_pathspec(const char **pathspec, const char *name, int namelen,
                int prefix, char *seen)
@@ -267,12 +276,19 @@ static int match_pathspec_item(const struct pathspec_item *item, int prefix,
 }
 
 /*
- * Given a name and a list of pathspecs, see if the name matches
- * any of the pathspecs.  The caller is also interested in seeing
- * all pathspec matches some names it calls this function with
- * (otherwise the user could have mistyped the unmatched pathspec),
- * and a mark is left in seen[] array for pathspec element that
- * actually matched anything.
+ * Given a name and a list of pathspecs, returns the nature of the
+ * closest (i.e. most specific) match of the name to any of the
+ * pathspecs.
+ *
+ * The caller typically calls this multiple times with the same
+ * pathspec and seen[] array but with different name/namelen
+ * (e.g. entries from the index) and is interested in seeing if and
+ * how each pathspec matches all the names it calls this function
+ * with.  A mark is left in the seen[] array for each pathspec element
+ * indicating the closest type of match that element achieved, so if
+ * seen[n] remains zero after multiple invocations, that means the nth
+ * pathspec did not match any names, which could indicate that the
+ * user mistyped the nth pathspec.
  */
 int match_pathspec_depth(const struct pathspec *ps,
                         const char *name, int namelen,
@@ -377,7 +393,7 @@ void parse_exclude_pattern(const char **pattern,
 }
 
 void add_exclude(const char *string, const char *base,
-                int baselen, struct exclude_list *which)
+                int baselen, struct exclude_list *el, int srcpos)
 {
        struct exclude *x;
        int patternlen;
@@ -401,8 +417,10 @@ void add_exclude(const char *string, const char *base,
        x->base = base;
        x->baselen = baselen;
        x->flags = flags;
-       ALLOC_GROW(which->excludes, which->nr + 1, which->alloc);
-       which->excludes[which->nr++] = x;
+       x->srcpos = srcpos;
+       ALLOC_GROW(el->excludes, el->nr + 1, el->alloc);
+       el->excludes[el->nr++] = x;
+       x->el = el;
 }
 
 static void *read_skip_worktree_file_from_index(const char *path, size_t *size)
@@ -428,27 +446,32 @@ static void *read_skip_worktree_file_from_index(const char *path, size_t *size)
        return data;
 }
 
-void free_excludes(struct exclude_list *el)
+/*
+ * Frees memory within el which was allocated for exclude patterns and
+ * the file buffer.  Does not free el itself.
+ */
+void clear_exclude_list(struct exclude_list *el)
 {
        int i;
 
        for (i = 0; i < el->nr; i++)
                free(el->excludes[i]);
        free(el->excludes);
+       free(el->filebuf);
 
        el->nr = 0;
        el->excludes = NULL;
+       el->filebuf = NULL;
 }
 
 int add_excludes_from_file_to_list(const char *fname,
                                   const char *base,
                                   int baselen,
-                                  char **buf_p,
-                                  struct exclude_list *which,
+                                  struct exclude_list *el,
                                   int check_index)
 {
        struct stat st;
-       int fd, i;
+       int fd, i, lineno = 1;
        size_t size = 0;
        char *buf, *entry;
 
@@ -486,30 +509,53 @@ int add_excludes_from_file_to_list(const char *fname,
                close(fd);
        }
 
-       if (buf_p)
-               *buf_p = buf;
+       el->filebuf = buf;
        entry = buf;
        for (i = 0; i < size; i++) {
                if (buf[i] == '\n') {
                        if (entry != buf + i && entry[0] != '#') {
                                buf[i - (i && buf[i-1] == '\r')] = 0;
-                               add_exclude(entry, base, baselen, which);
+                               add_exclude(entry, base, baselen, el, lineno);
                        }
+                       lineno++;
                        entry = buf + i + 1;
                }
        }
        return 0;
 }
 
+struct exclude_list *add_exclude_list(struct dir_struct *dir,
+                                     int group_type, const char *src)
+{
+       struct exclude_list *el;
+       struct exclude_list_group *group;
+
+       group = &dir->exclude_list_group[group_type];
+       ALLOC_GROW(group->el, group->nr + 1, group->alloc);
+       el = &group->el[group->nr++];
+       memset(el, 0, sizeof(*el));
+       el->src = src;
+       return el;
+}
+
+/*
+ * Used to set up core.excludesfile and .git/info/exclude lists.
+ */
 void add_excludes_from_file(struct dir_struct *dir, const char *fname)
 {
-       if (add_excludes_from_file_to_list(fname, "", 0, NULL,
-                                          &dir->exclude_list[EXC_FILE], 0) < 0)
+       struct exclude_list *el;
+       el = add_exclude_list(dir, EXC_FILE, fname);
+       if (add_excludes_from_file_to_list(fname, "", 0, el, 0) < 0)
                die("cannot use %s as an exclude file", fname);
 }
 
+/*
+ * Loads the per-directory exclude list for the substring of base
+ * which has a char length of baselen.
+ */
 static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
 {
+       struct exclude_list_group *group;
        struct exclude_list *el;
        struct exclude_stack *stk = NULL;
        int current;
@@ -518,17 +564,21 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
            (baselen + strlen(dir->exclude_per_dir) >= PATH_MAX))
                return; /* too long a path -- ignore */
 
-       /* Pop the ones that are not the prefix of the path being checked. */
-       el = &dir->exclude_list[EXC_DIRS];
+       group = &dir->exclude_list_group[EXC_DIRS];
+
+       /* Pop the exclude lists from the EXCL_DIRS exclude_list_group
+        * which originate from directories not in the prefix of the
+        * path being checked. */
        while ((stk = dir->exclude_stack) != NULL) {
                if (stk->baselen <= baselen &&
                    !strncmp(dir->basebuf, base, stk->baselen))
                        break;
+               el = &group->el[dir->exclude_stack->exclude_ix];
                dir->exclude_stack = stk->prev;
-               while (stk->exclude_ix < el->nr)
-                       free(el->excludes[--el->nr]);
-               free(stk->filebuf);
+               free((char *)el->src); /* see strdup() below */
+               clear_exclude_list(el);
                free(stk);
+               group->nr--;
        }
 
        /* Read from the parent directories and push them down. */
@@ -549,13 +599,22 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
                }
                stk->prev = dir->exclude_stack;
                stk->baselen = cp - base;
-               stk->exclude_ix = el->nr;
                memcpy(dir->basebuf + current, base + current,
                       stk->baselen - current);
                strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir);
+               /*
+                * dir->basebuf gets reused by the traversal, but we
+                * need fname to remain unchanged to ensure the src
+                * member of each struct exclude correctly
+                * back-references its source file.  Other invocations
+                * of add_exclude_list provide stable strings, so we
+                * strdup() and free() here in the caller.
+                */
+               el = add_exclude_list(dir, EXC_DIRS, strdup(dir->basebuf));
+               stk->exclude_ix = group->nr - 1;
                add_excludes_from_file_to_list(dir->basebuf,
                                               dir->basebuf, stk->baselen,
-                                              &stk->filebuf, el, 1);
+                                              el, 1);
                dir->exclude_stack = stk;
                current = stk->baselen;
        }
@@ -626,25 +685,30 @@ int match_pathname(const char *pathname, int pathlen,
        }
 
        return wildmatch(pattern, name,
-                        ignore_case ? FNM_CASEFOLD : 0) == 0;
+                        WM_PATHNAME | (ignore_case ? WM_CASEFOLD : 0),
+                        NULL) == 0;
 }
 
-/* Scan the list and let the last match determine the fate.
- * Return 1 for exclude, 0 for include and -1 for undecided.
+/*
+ * 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
+ * any, determines the fate.  Returns the exclude_list element which
+ * matched, or NULL for undecided.
  */
-int excluded_from_list(const char *pathname,
-                      int pathlen, const char *basename, int *dtype,
-                      struct exclude_list *el)
+static struct exclude *last_exclude_matching_from_list(const char *pathname,
+                                                      int pathlen,
+                                                      const char *basename,
+                                                      int *dtype,
+                                                      struct exclude_list *el)
 {
        int i;
 
        if (!el->nr)
-               return -1;      /* undefined */
+               return NULL;    /* undefined */
 
        for (i = el->nr - 1; 0 <= i; i--) {
                struct exclude *x = el->excludes[i];
                const char *exclude = x->pattern;
-               int to_exclude = x->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
                int prefix = x->nowildcardlen;
 
                if (x->flags & EXC_FLAG_MUSTBEDIR) {
@@ -659,7 +723,7 @@ int excluded_from_list(const char *pathname,
                                           pathlen - (basename - pathname),
                                           exclude, prefix, x->patternlen,
                                           x->flags))
-                               return to_exclude;
+                               return x;
                        continue;
                }
 
@@ -667,28 +731,69 @@ int excluded_from_list(const char *pathname,
                if (match_pathname(pathname, pathlen,
                                   x->base, x->baselen ? x->baselen - 1 : 0,
                                   exclude, prefix, x->patternlen, x->flags))
-                       return to_exclude;
+                       return x;
        }
+       return NULL; /* undecided */
+}
+
+/*
+ * Scan the list and let the last match determine the fate.
+ * Return 1 for exclude, 0 for include and -1 for undecided.
+ */
+int is_excluded_from_list(const char *pathname,
+                         int pathlen, const char *basename, int *dtype,
+                         struct exclude_list *el)
+{
+       struct exclude *exclude;
+       exclude = last_exclude_matching_from_list(pathname, pathlen, basename, dtype, el);
+       if (exclude)
+               return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
        return -1; /* undecided */
 }
 
-static int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
+/*
+ * Loads the exclude lists for the directory containing pathname, then
+ * scans all exclude lists to determine whether pathname is excluded.
+ * Returns the exclude_list element which matched, or NULL for
+ * undecided.
+ */
+static struct exclude *last_exclude_matching(struct dir_struct *dir,
+                                            const char *pathname,
+                                            int *dtype_p)
 {
        int pathlen = strlen(pathname);
-       int st;
+       int i, j;
+       struct exclude_list_group *group;
+       struct exclude *exclude;
        const char *basename = strrchr(pathname, '/');
        basename = (basename) ? basename+1 : pathname;
 
        prep_exclude(dir, pathname, basename-pathname);
-       for (st = EXC_CMDL; st <= EXC_FILE; st++) {
-               switch (excluded_from_list(pathname, pathlen, basename,
-                                          dtype_p, &dir->exclude_list[st])) {
-               case 0:
-                       return 0;
-               case 1:
-                       return 1;
+
+       for (i = EXC_CMDL; i <= EXC_FILE; i++) {
+               group = &dir->exclude_list_group[i];
+               for (j = group->nr - 1; j >= 0; j--) {
+                       exclude = last_exclude_matching_from_list(
+                               pathname, pathlen, basename, dtype_p,
+                               &group->el[j]);
+                       if (exclude)
+                               return exclude;
                }
        }
+       return NULL;
+}
+
+/*
+ * Loads the exclude lists for the directory containing pathname, then
+ * scans all exclude lists to determine whether pathname is excluded.
+ * Returns 1 if true, otherwise 0.
+ */
+static int is_excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
+{
+       struct exclude *exclude =
+               last_exclude_matching(dir, pathname, dtype_p);
+       if (exclude)
+               return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
        return 0;
 }
 
@@ -696,6 +801,7 @@ void path_exclude_check_init(struct path_exclude_check *check,
                             struct dir_struct *dir)
 {
        check->dir = dir;
+       check->exclude = NULL;
        strbuf_init(&check->path, 256);
 }
 
@@ -705,32 +811,41 @@ void path_exclude_check_clear(struct path_exclude_check *check)
 }
 
 /*
- * 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.
+ * For each subdirectory in name, starting with the top-most, checks
+ * to see if that subdirectory is excluded, and if so, returns the
+ * corresponding exclude structure.  Otherwise, checks whether name
+ * itself (which is presumably a file) is excluded.
  *
  * 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)
+struct exclude *last_exclude_matching_path(struct path_exclude_check *check,
+                                          const char *name, int namelen,
+                                          int *dtype)
 {
        int i;
        struct strbuf *path = &check->path;
+       struct exclude *exclude;
 
        /*
         * 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.
+        * is_excluded() on the whole name string.
         */
        if (namelen < 0)
                namelen = strlen(name);
 
+       /*
+        * If path is non-empty, and name is equal to path or a
+        * subdirectory of path, name should be excluded, because
+        * it's inside a directory which is already known to be
+        * excluded and was previously left in check->path.
+        */
        if (path->len &&
            path->len <= namelen &&
            !memcmp(name, path->buf, path->len) &&
            (!name[path->len] || name[path->len] == '/'))
-               return 1;
+               return check->exclude;
 
        strbuf_setlen(path, 0);
        for (i = 0; name[i]; i++) {
@@ -738,8 +853,12 @@ int path_excluded(struct path_exclude_check *check,
 
                if (ch == '/') {
                        int dt = DT_DIR;
-                       if (excluded(check->dir, path->buf, &dt))
-                               return 1;
+                       exclude = last_exclude_matching(check->dir,
+                                                       path->buf, &dt);
+                       if (exclude) {
+                               check->exclude = exclude;
+                               return exclude;
+                       }
                }
                strbuf_addch(path, ch);
        }
@@ -747,7 +866,22 @@ int path_excluded(struct path_exclude_check *check,
        /* An entry in the index; cannot be a directory with subentries */
        strbuf_setlen(path, 0);
 
-       return excluded(check->dir, name, dtype);
+       return last_exclude_matching(check->dir, name, dtype);
+}
+
+/*
+ * 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.
+ */
+int is_path_excluded(struct path_exclude_check *check,
+                 const char *name, int namelen, int *dtype)
+{
+       struct exclude *exclude =
+               last_exclude_matching_path(check, name, namelen, dtype);
+       if (exclude)
+               return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
+       return 0;
 }
 
 static struct dir_entry *dir_entry_new(const char *pathname, int len)
@@ -763,7 +897,8 @@ static struct dir_entry *dir_entry_new(const char *pathname, int len)
 
 static struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len)
 {
-       if (cache_name_exists(pathname, len, ignore_case))
+       if (!(dir->flags & DIR_SHOW_IGNORED) &&
+           cache_name_exists(pathname, len, ignore_case))
                return NULL;
 
        ALLOC_GROW(dir->entries, dir->nr+1, dir->alloc);
@@ -865,8 +1000,9 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len)
  * traversal routine.
  *
  * Case 1: If we *already* have entries in the index under that
- * directory name, we always recurse into the directory to see
- * all the files.
+ * directory name, we recurse into the directory to see all the files,
+ * unless the directory is excluded and we want to show ignored
+ * directories
  *
  * Case 2: If we *already* have that directory name as a gitlink,
  * we always continue to see it as a gitlink, regardless of whether
@@ -880,6 +1016,9 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len)
  *      just a directory, unless "hide_empty_directories" is
  *      also true and the directory is empty, in which case
  *      we just ignore it entirely.
+ *      if we are looking for ignored directories, look if it
+ *      contains only ignored files to decide if it must be shown as
+ *      ignored or not.
  *  (b) if it looks like a git directory, and we don't have
  *      'no_gitlinks' set we treat it as a gitlink, and show it
  *      as a directory.
@@ -892,12 +1031,15 @@ enum directory_treatment {
 };
 
 static enum directory_treatment treat_directory(struct dir_struct *dir,
-       const char *dirname, int len,
+       const char *dirname, int len, int exclude,
        const struct path_simplify *simplify)
 {
        /* The "len-1" is to strip the final '/' */
        switch (directory_exists_in_index(dirname, len-1)) {
        case index_directory:
+               if ((dir->flags & DIR_SHOW_OTHER_DIRECTORIES) && exclude)
+                       break;
+
                return recurse_into_directory;
 
        case index_gitdir:
@@ -917,13 +1059,68 @@ static enum directory_treatment treat_directory(struct dir_struct *dir,
        }
 
        /* This is the "show_other_directories" case */
-       if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
+
+       /*
+        * We are looking for ignored files and our directory is not ignored,
+        * check if it contains only ignored files
+        */
+       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 (!(dir->flags & DIR_SHOW_IGNORED) &&
+           !(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
                return show_directory;
        if (!read_directory_recursive(dir, dirname, len, 1, simplify))
                return ignore_directory;
        return show_directory;
 }
 
+/*
+ * Decide what to do when we find a file while traversing the
+ * filesystem. Mostly two cases:
+ *
+ *  1. We are looking for ignored files
+ *   (a) File is ignored, include it
+ *   (b) File is in ignored path, include it
+ *   (c) File is not ignored, exclude it
+ *
+ *  2. Other scenarios, include the file if not excluded
+ *
+ * Return 1 for exclude, 0 for include.
+ */
+static int treat_file(struct dir_struct *dir, struct strbuf *path, int exclude, int *dtype)
+{
+       struct path_exclude_check check;
+       int exclude_file = 0;
+
+       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))
+                       exclude_file = 1;
+
+               path_exclude_check_clear(&check);
+       }
+
+       return exclude_file;
+}
+
 /*
  * This is an inexact early pruning of any recursive directory
  * reading - if the path cannot possibly be in the pathspec,
@@ -1047,7 +1244,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
                                          const struct path_simplify *simplify,
                                          int dtype, struct dirent *de)
 {
-       int exclude = excluded(dir, path->buf, &dtype);
+       int exclude = is_excluded(dir, path->buf, &dtype);
        if (exclude && (dir->flags & DIR_COLLECT_IGNORED)
            && exclude_matches_pathspec(path->buf, path->len, simplify))
                dir_add_ignored(dir, path->buf, path->len);
@@ -1062,27 +1259,14 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
        if (dtype == DT_UNKNOWN)
                dtype = get_dtype(de, path->buf, path->len);
 
-       /*
-        * Do we want to see just the ignored files?
-        * We still need to recurse into directories,
-        * even if we don't ignore them, since the
-        * directory may contain files that we do..
-        */
-       if (!exclude && (dir->flags & DIR_SHOW_IGNORED)) {
-               if (dtype != DT_DIR)
-                       return path_ignored;
-       }
-
        switch (dtype) {
        default:
                return path_ignored;
        case DT_DIR:
                strbuf_addch(path, '/');
-               switch (treat_directory(dir, path->buf, path->len, simplify)) {
+
+               switch (treat_directory(dir, path->buf, path->len, exclude, simplify)) {
                case show_directory:
-                       if (exclude != !!(dir->flags
-                                         & DIR_SHOW_IGNORED))
-                               return path_ignored;
                        break;
                case recurse_into_directory:
                        return path_recurse;
@@ -1092,7 +1276,12 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
                break;
        case DT_REG:
        case DT_LNK:
-               break;
+               switch (treat_file(dir, path, exclude, &dtype)) {
+               case 1:
+                       return path_ignored;
+               default:
+                       break;
+               }
        }
        return path_handled;
 }
@@ -1493,3 +1682,33 @@ int limit_pathspec_to_literal(void)
                flag = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0);
        return flag;
 }
+
+/*
+ * Frees memory within dir which was allocated for exclude lists and
+ * the exclude_stack.  Does not free dir itself.
+ */
+void clear_directory(struct dir_struct *dir)
+{
+       int i, j;
+       struct exclude_list_group *group;
+       struct exclude_list *el;
+       struct exclude_stack *stk;
+
+       for (i = EXC_CMDL; i <= EXC_FILE; i++) {
+               group = &dir->exclude_list_group[i];
+               for (j = 0; j < group->nr; j++) {
+                       el = &group->el[j];
+                       if (i == EXC_DIRS)
+                               free((char *)el->src);
+                       clear_exclude_list(el);
+               }
+               free(group->el);
+       }
+
+       stk = dir->exclude_stack;
+       while (stk) {
+               struct exclude_stack *prev = stk->prev;
+               free(stk);
+               stk = prev;
+       }
+}