grep with unmerged index
[gitweb.git] / dir.c
diff --git a/dir.c b/dir.c
index 7426fde330a200e3137e722c4b9adbc5ce6bdd90..f843c4dd208ac0f37f9c70383e522590688f1966 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -7,12 +7,17 @@
  */
 #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)
 {
        const char *path, *slash, *next;
@@ -29,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;
@@ -265,36 +271,139 @@ int excluded(struct dir_struct *dir, const char *pathname)
        return 0;
 }
 
-struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len)
-{
+static struct dir_entry *dir_entry_new(const char *pathname, int len) {
        struct dir_entry *ent;
 
-       if (cache_name_pos(pathname, len) >= 0)
-               return NULL;
-
-       if (dir->nr == dir->alloc) {
-               int alloc = alloc_nr(dir->alloc);
-               dir->alloc = alloc;
-               dir->entries = xrealloc(dir->entries, alloc*sizeof(ent));
-       }
        ent = xmalloc(sizeof(*ent) + len + 1);
-       ent->ignored = ent->ignored_dir = 0;
        ent->len = len;
        memcpy(ent->name, pathname, len);
        ent->name[len] = 0;
-       dir->entries[dir->nr++] = ent;
        return ent;
 }
 
-static int dir_exists(const char *dirname, int len)
+struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len)
+{
+       if (cache_name_pos(pathname, len) >= 0)
+               return NULL;
+
+       ALLOC_GROW(dir->entries, dir->nr+1, dir->alloc);
+       return dir->entries[dir->nr++] = dir_entry_new(pathname, len);
+}
+
+struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len)
+{
+       if (cache_name_pos(pathname, len) >= 0)
+               return NULL;
+
+       ALLOC_GROW(dir->ignored, dir->ignored_nr+1, dir->ignored_alloc);
+       return dir->ignored[dir->ignored_nr++] = dir_entry_new(pathname, 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)
-               return 1;
-       pos = -pos-1;
-       if (pos >= active_nr) /* can't */
-               return 0;
-       return !strncmp(active_cache[pos]->name, dirname, len);
+       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_ISGITLINK(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_gitlinks' 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_gitlinks) {
+                       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;
 }
 
 /*
@@ -322,6 +431,36 @@ static int simplify_away(const char *path, int pathlen, const struct path_simpli
        return 0;
 }
 
+static int in_pathspec(const char *path, int len, const struct path_simplify *simplify)
+{
+       if (simplify) {
+               for (; simplify->path; simplify++) {
+                       if (len == simplify->len
+                           && !memcmp(path, simplify->path, len))
+                               return 1;
+               }
+       }
+       return 0;
+}
+
+static int get_dtype(struct dirent *de, const char *path)
+{
+       int dtype = DTYPE(de);
+       struct stat st;
+
+       if (dtype != DT_UNKNOWN)
+               return dtype;
+       if (lstat(path, &st))
+               return dtype;
+       if (S_ISREG(st.st_mode))
+               return DT_REG;
+       if (S_ISDIR(st.st_mode))
+               return DT_DIR;
+       if (S_ISLNK(st.st_mode))
+               return DT_LNK;
+       return dtype;
+}
+
 /*
  * Read a directory tree. We currently ignore anything but
  * directories, regular files and symlinks. That's because git
@@ -345,7 +484,8 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
                exclude_stk = push_exclude_per_directory(dir, base, baselen);
 
                while ((de = readdir(fdir)) != NULL) {
-                       int len;
+                       int len, dtype;
+                       int exclude;
 
                        if ((de->d_name[0] == '.') &&
                            (de->d_name[1] == 0 ||
@@ -353,43 +493,57 @@ 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) {
+
+                       exclude = excluded(dir, fullname);
+                       if (exclude && dir->collect_ignored
+                           && in_pathspec(fullname, baselen + len, simplify))
+                               dir_add_ignored(dir, fullname, baselen + len);
+
+                       /*
+                        * Excluded? If we don't explicitly want to show
+                        * ignored files, ignore it
+                        */
+                       if (exclude && !dir->show_ignored)
+                               continue;
+
+                       dtype = get_dtype(de, fullname);
+
+                       /*
+                        * 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->show_ignored) {
+                               if (dtype != DT_DIR)
                                        continue;
-                               }
                        }
 
-                       switch (DTYPE(de)) {
-                       struct stat st;
+                       switch (dtype) {
                        default:
                                continue;
-                       case DT_UNKNOWN:
-                               if (lstat(fullname, &st))
-                                       continue;
-                               if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
-                                       break;
-                               if (!S_ISDIR(st.st_mode))
-                                       continue;
-                               /* fallthrough */
                        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, simplify))
+                               switch (treat_directory(dir, fullname, baselen + len, simplify)) {
+                               case show_directory:
+                                       if (exclude != dir->show_ignored)
                                                continue;
                                        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, simplify);
-                               continue;
+                               break;
                        case DT_REG:
                        case DT_LNK:
                                break;
@@ -502,6 +656,7 @@ int read_directory(struct dir_struct *dir, const char *path, const char *base, i
        read_directory_recursive(dir, path, base, baselen, 0, simplify);
        free_simplify(simplify);
        qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
+       qsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name);
        return dir->nr;
 }
 
@@ -511,3 +666,46 @@ file_exists(const char *f)
   struct stat sb;
   return stat(f, &sb) == 0;
 }
+
+/*
+ * get_relative_cwd() gets the prefix of the current working directory
+ * relative to 'dir'.  If we are not inside 'dir', it returns NULL.
+ *
+ * As a convenience, it also returns NULL if 'dir' is already NULL.  The
+ * reason for this behaviour is that it is natural for functions returning
+ * directory names to return NULL to say "this directory does not exist"
+ * or "this directory is invalid".  These cases are usually handled the
+ * same as if the cwd is not inside 'dir' at all, so get_relative_cwd()
+ * returns NULL for both of them.
+ *
+ * Most notably, get_relative_cwd(buffer, size, get_git_work_tree())
+ * unifies the handling of "outside work tree" with "no work tree at all".
+ */
+char *get_relative_cwd(char *buffer, int size, const char *dir)
+{
+       char *cwd = buffer;
+
+       if (!dir)
+               return NULL;
+       if (!getcwd(buffer, size))
+               die("can't find the current directory: %s", strerror(errno));
+
+       if (!is_absolute_path(dir))
+               dir = make_absolute_path(dir);
+
+       while (*dir && *dir == *cwd) {
+               dir++;
+               cwd++;
+       }
+       if (*dir)
+               return NULL;
+       if (*cwd == '/')
+               return cwd + 1;
+       return cwd;
+}
+
+int is_inside_dir(const char *dir)
+{
+       char buffer[PATH_MAX];
+       return get_relative_cwd(buffer, sizeof(buffer), dir) != NULL;
+}