Merge branch 'mk/diff'
[gitweb.git] / dir.c
diff --git a/dir.c b/dir.c
index 32b57f0125d1b9e95345eb23a9a32fbf8444b1b8..d3063520b03c9c5f608cbd9e97f6b414adb4c5af 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -7,6 +7,16 @@
  */
 #include "cache.h"
 #include "dir.h"
+#include "refs.h"
+
+struct path_simplify {
+       int len;
+       const char *path;
+};
+
+static int read_directory_recursive(struct dir_struct *dir,
+       const char *path, const char *base, int baselen,
+       int check_only, const struct path_simplify *simplify);
 
 int common_prefix(const char **pathspec)
 {
@@ -24,8 +34,9 @@ int common_prefix(const char **pathspec)
        prefix = slash - path + 1;
        while ((next = *++pathspec) != NULL) {
                int len = strlen(next);
-               if (len >= prefix && !memcmp(path, next, len))
+               if (len >= prefix && !memcmp(path, next, prefix))
                        continue;
+               len = prefix - 1;
                for (;;) {
                        if (!len)
                                return 0;
@@ -130,13 +141,13 @@ static int add_excludes_from_file_1(const char *fname,
 {
        struct stat st;
        int fd, i;
-       long size;
+       size_t size;
        char *buf, *entry;
 
        fd = open(fname, O_RDONLY);
        if (fd < 0 || fstat(fd, &st) < 0)
                goto err;
-       size = st.st_size;
+       size = xsize_t(st.st_size);
        if (size == 0) {
                close(fd);
                return 0;
@@ -281,15 +292,136 @@ struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int
        return ent;
 }
 
-static int dir_exists(const char *dirname, int len)
+enum exist_status {
+       index_nonexistent = 0,
+       index_directory,
+       index_gitdir,
+};
+
+/*
+ * The index sorts alphabetically by entry name, which
+ * means that a gitlink sorts as '\0' at the end, while
+ * a directory (which is defined not as an entry, but as
+ * the files it contains) will sort with the '/' at the
+ * end.
+ */
+static enum exist_status directory_exists_in_index(const char *dirname, int len)
 {
        int pos = cache_name_pos(dirname, len);
-       if (pos >= 0)
+       if (pos < 0)
+               pos = -pos-1;
+       while (pos < active_nr) {
+               struct cache_entry *ce = active_cache[pos++];
+               unsigned char endchar;
+
+               if (strncmp(ce->name, dirname, len))
+                       break;
+               endchar = ce->name[len];
+               if (endchar > '/')
+                       break;
+               if (endchar == '/')
+                       return index_directory;
+               if (!endchar && S_ISDIRLNK(ntohl(ce->ce_mode)))
+                       return index_gitdir;
+       }
+       return index_nonexistent;
+}
+
+/*
+ * When we find a directory when traversing the filesystem, we
+ * have three distinct cases:
+ *
+ *  - ignore it
+ *  - see it as a directory
+ *  - recurse into it
+ *
+ * and which one we choose depends on a combination of existing
+ * git index contents and the flags passed into the directory
+ * 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.
+ *
+ * Case 2: If we *already* have that directory name as a gitlink,
+ * we always continue to see it as a gitlink, regardless of whether
+ * there is an actual git directory there or not (it might not
+ * be checked out as a subproject!)
+ *
+ * Case 3: if we didn't have it in the index previously, we
+ * have a few sub-cases:
+ *
+ *  (a) if "show_other_directories" is true, we show it as
+ *      just a directory, unless "hide_empty_directories" is
+ *      also true and the directory is empty, in which case
+ *      we just ignore it entirely.
+ *  (b) if it looks like a git directory, and we don't have
+ *      'no_dirlinks' set we treat it as a gitlink, and show it
+ *      as a directory.
+ *  (c) otherwise, we recurse into it.
+ */
+enum directory_treatment {
+       show_directory,
+       ignore_directory,
+       recurse_into_directory,
+};
+
+static enum directory_treatment treat_directory(struct dir_struct *dir,
+       const char *dirname, int len,
+       const struct path_simplify *simplify)
+{
+       /* The "len-1" is to strip the final '/' */
+       switch (directory_exists_in_index(dirname, len-1)) {
+       case index_directory:
+               return recurse_into_directory;
+
+       case index_gitdir:
+               if (dir->show_other_directories)
+                       return ignore_directory;
+               return show_directory;
+
+       case index_nonexistent:
+               if (dir->show_other_directories)
+                       break;
+               if (!dir->no_dirlinks) {
+                       unsigned char sha1[20];
+                       if (resolve_gitlink_ref(dirname, "HEAD", sha1) == 0)
+                               return show_directory;
+               }
+               return recurse_into_directory;
+       }
+
+       /* This is the "show_other_directories" case */
+       if (!dir->hide_empty_directories)
+               return show_directory;
+       if (!read_directory_recursive(dir, dirname, dirname, len, 1, simplify))
+               return ignore_directory;
+       return show_directory;
+}
+
+/*
+ * This is an inexact early pruning of any recursive directory
+ * reading - if the path cannot possibly be in the pathspec,
+ * return true, and we'll skip it early.
+ */
+static int simplify_away(const char *path, int pathlen, const struct path_simplify *simplify)
+{
+       if (simplify) {
+               for (;;) {
+                       const char *match = simplify->path;
+                       int len = simplify->len;
+
+                       if (!match)
+                               break;
+                       if (len > pathlen)
+                               len = pathlen;
+                       if (!memcmp(path, match, len))
+                               return 0;
+                       simplify++;
+               }
                return 1;
-       pos = -pos-1;
-       if (pos >= active_nr) /* can't */
-               return 0;
-       return !strncmp(active_cache[pos]->name, dirname, len);
+       }
+       return 0;
 }
 
 /*
@@ -301,7 +433,7 @@ static int dir_exists(const char *dirname, int len)
  * Also, we ignore the name ".git" (even if it is not a directory).
  * That likely will not change.
  */
-static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen, int check_only)
+static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen, int check_only, const struct path_simplify *simplify)
 {
        DIR *fdir = opendir(path);
        int contents = 0;
@@ -323,7 +455,12 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
                             !strcmp(de->d_name + 1, "git")))
                                continue;
                        len = strlen(de->d_name);
+                       /* Ignore overly long pathnames! */
+                       if (len + baselen + 8 > sizeof(fullname))
+                               continue;
                        memcpy(fullname + baselen, de->d_name, len+1);
+                       if (simplify_away(fullname, baselen + len, simplify))
+                               continue;
                        if (excluded(dir, fullname) != dir->show_ignored) {
                                if (!dir->show_ignored || DTYPE(de) != DT_DIR) {
                                        continue;
@@ -345,19 +482,17 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
                        case DT_DIR:
                                memcpy(fullname + baselen + len, "/", 2);
                                len++;
-                               if (dir->show_other_directories &&
-                                   !dir_exists(fullname, baselen + len)) {
-                                       if (dir->hide_empty_directories &&
-                                           !read_directory_recursive(dir,
-                                                   fullname, fullname,
-                                                   baselen + len, 1))
-                                               continue;
+                               switch (treat_directory(dir, fullname, baselen + len, simplify)) {
+                               case show_directory:
                                        break;
+                               case recurse_into_directory:
+                                       contents += read_directory_recursive(dir,
+                                               fullname, fullname, baselen + len, 0, simplify);
+                                       continue;
+                               case ignore_directory:
+                                       continue;
                                }
-
-                               contents += read_directory_recursive(dir,
-                                       fullname, fullname, baselen + len, 0);
-                               continue;
+                               break;
                        case DT_REG:
                        case DT_LNK:
                                break;
@@ -386,8 +521,61 @@ static int cmp_name(const void *p1, const void *p2)
                                  e2->name, e2->len);
 }
 
-int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen)
+/*
+ * Return the length of the "simple" part of a path match limiter.
+ */
+static int simple_length(const char *match)
+{
+       const char special[256] = {
+               [0] = 1, ['?'] = 1,
+               ['\\'] = 1, ['*'] = 1,
+               ['['] = 1
+       };
+       int len = -1;
+
+       for (;;) {
+               unsigned char c = *match++;
+               len++;
+               if (special[c])
+                       return len;
+       }
+}
+
+static struct path_simplify *create_simplify(const char **pathspec)
+{
+       int nr, alloc = 0;
+       struct path_simplify *simplify = NULL;
+
+       if (!pathspec)
+               return NULL;
+
+       for (nr = 0 ; ; nr++) {
+               const char *match;
+               if (nr >= alloc) {
+                       alloc = alloc_nr(alloc);
+                       simplify = xrealloc(simplify, alloc * sizeof(*simplify));
+               }
+               match = *pathspec++;
+               if (!match)
+                       break;
+               simplify[nr].path = match;
+               simplify[nr].len = simple_length(match);
+       }
+       simplify[nr].path = NULL;
+       simplify[nr].len = 0;
+       return simplify;
+}
+
+static void free_simplify(struct path_simplify *simplify)
 {
+       if (simplify)
+               free(simplify);
+}
+
+int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen, const char **pathspec)
+{
+       struct path_simplify *simplify = create_simplify(pathspec);
+
        /*
         * Make sure to do the per-directory exclude for all the
         * directories leading up to our base.
@@ -414,7 +602,8 @@ int read_directory(struct dir_struct *dir, const char *path, const char *base, i
                }
        }
 
-       read_directory_recursive(dir, path, base, baselen, 0);
+       read_directory_recursive(dir, path, base, baselen, 0, simplify);
+       free_simplify(simplify);
        qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
        return dir->nr;
 }