untracked cache: record/validate dir mtime and reuse cached output
[gitweb.git] / dir.c
diff --git a/dir.c b/dir.c
index 86bf6e9311cc7b08c89f9c362c7ed6b259a40bd8..54153741059811ad354d8ff6ed5587c8aba84adb 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -37,7 +37,12 @@ enum path_treatment {
 struct cached_dir {
        DIR *fdir;
        struct untracked_cache_dir *untracked;
+       int nr_files;
+       int nr_dirs;
+
        struct dirent *de;
+       const char *file;
+       struct untracked_cache_dir *ucd;
 };
 
 static enum path_treatment read_directory_recursive(struct dir_struct *dir,
@@ -607,6 +612,14 @@ static void invalidate_gitignore(struct untracked_cache *uc,
        do_invalidate_gitignore(dir);
 }
 
+static void invalidate_directory(struct untracked_cache *uc,
+                                struct untracked_cache_dir *dir)
+{
+       uc->dir_invalidated++;
+       dir->valid = 0;
+       dir->untracked_nr = 0;
+}
+
 /*
  * Given a file with name "fname", read it (either from disk, or from
  * the index if "check_index" is non-zero), parse it and store the
@@ -1425,6 +1438,39 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
        }
 }
 
+static enum path_treatment treat_path_fast(struct dir_struct *dir,
+                                          struct untracked_cache_dir *untracked,
+                                          struct cached_dir *cdir,
+                                          struct strbuf *path,
+                                          int baselen,
+                                          const struct path_simplify *simplify)
+{
+       strbuf_setlen(path, baselen);
+       if (!cdir->ucd) {
+               strbuf_addstr(path, cdir->file);
+               return path_untracked;
+       }
+       strbuf_addstr(path, cdir->ucd->name);
+       /* treat_one_path() does this before it calls treat_directory() */
+       if (path->buf[path->len - 1] != '/')
+               strbuf_addch(path, '/');
+       if (cdir->ucd->check_only)
+               /*
+                * check_only is set as a result of treat_directory() getting
+                * to its bottom. Verify again the same set of directories
+                * with check_only set.
+                */
+               return read_directory_recursive(dir, path->buf, path->len,
+                                               cdir->ucd, 1, simplify);
+       /*
+        * We get path_recurse in the first run when
+        * directory_exists_in_index() returns index_nonexistent. We
+        * are sure that new changes in the index does not impact the
+        * outcome. Return now.
+        */
+       return path_recurse;
+}
+
 static enum path_treatment treat_path(struct dir_struct *dir,
                                      struct untracked_cache_dir *untracked,
                                      struct cached_dir *cdir,
@@ -1435,6 +1481,9 @@ static enum path_treatment treat_path(struct dir_struct *dir,
        int dtype;
        struct dirent *de = cdir->de;
 
+       if (!de)
+               return treat_path_fast(dir, untracked, cdir, path,
+                                      baselen, simplify);
        if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git"))
                return path_none;
        strbuf_setlen(path, baselen);
@@ -1455,6 +1504,52 @@ static void add_untracked(struct untracked_cache_dir *dir, const char *name)
        dir->untracked[dir->untracked_nr++] = xstrdup(name);
 }
 
+static int valid_cached_dir(struct dir_struct *dir,
+                           struct untracked_cache_dir *untracked,
+                           struct strbuf *path,
+                           int check_only)
+{
+       struct stat st;
+
+       if (!untracked)
+               return 0;
+
+       if (stat(path->len ? path->buf : ".", &st)) {
+               invalidate_directory(dir->untracked, untracked);
+               memset(&untracked->stat_data, 0, sizeof(untracked->stat_data));
+               return 0;
+       }
+       if (!untracked->valid ||
+           match_stat_data(&untracked->stat_data, &st)) {
+               if (untracked->valid)
+                       invalidate_directory(dir->untracked, untracked);
+               fill_stat_data(&untracked->stat_data, &st);
+               return 0;
+       }
+
+       if (untracked->check_only != !!check_only) {
+               invalidate_directory(dir->untracked, untracked);
+               return 0;
+       }
+
+       /*
+        * prep_exclude will be called eventually on this directory,
+        * but it's called much later in last_exclude_matching(). We
+        * need it now to determine the validity of the cache for this
+        * path. The next calls will be nearly no-op, the way
+        * prep_exclude() is designed.
+        */
+       if (path->len && path->buf[path->len - 1] != '/') {
+               strbuf_addch(path, '/');
+               prep_exclude(dir, path->buf, path->len);
+               strbuf_setlen(path, path->len - 1);
+       } else
+               prep_exclude(dir, path->buf, path->len);
+
+       /* hopefully prep_exclude() haven't invalidated this entry... */
+       return untracked->valid;
+}
+
 static int open_cached_dir(struct cached_dir *cdir,
                           struct dir_struct *dir,
                           struct untracked_cache_dir *untracked,
@@ -1463,7 +1558,11 @@ static int open_cached_dir(struct cached_dir *cdir,
 {
        memset(cdir, 0, sizeof(*cdir));
        cdir->untracked = untracked;
+       if (valid_cached_dir(dir, untracked, path, check_only))
+               return 0;
        cdir->fdir = opendir(path->len ? path->buf : ".");
+       if (dir->untracked)
+               dir->untracked->dir_opened++;
        if (!cdir->fdir)
                return -1;
        return 0;
@@ -1477,6 +1576,18 @@ static int read_cached_dir(struct cached_dir *cdir)
                        return -1;
                return 0;
        }
+       while (cdir->nr_dirs < cdir->untracked->dirs_nr) {
+               struct untracked_cache_dir *d = cdir->untracked->dirs[cdir->nr_dirs];
+               cdir->ucd = d;
+               cdir->nr_dirs++;
+               return 0;
+       }
+       cdir->ucd = NULL;
+       if (cdir->nr_files < cdir->untracked->untracked_nr) {
+               struct untracked_cache_dir *d = cdir->untracked;
+               cdir->file = d->untracked[cdir->nr_files++];
+               return 0;
+       }
        return -1;
 }
 
@@ -1484,6 +1595,12 @@ static void close_cached_dir(struct cached_dir *cdir)
 {
        if (cdir->fdir)
                closedir(cdir->fdir);
+       /*
+        * We have gone through this directory and found no untracked
+        * entries. Mark it valid.
+        */
+       if (cdir->untracked)
+               cdir->untracked->valid = 1;
 }
 
 /*
@@ -1537,7 +1654,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
                if (check_only) {
                        /* abort early if maximum state has been reached */
                        if (dir_state == path_untracked) {
-                               if (untracked)
+                               if (cdir.fdir)
                                        add_untracked(untracked, path.buf + baselen);
                                break;
                        }
@@ -1561,7 +1678,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
                        if (dir->flags & DIR_SHOW_IGNORED)
                                break;
                        dir_add_name(dir, path.buf, path.len);
-                       if (untracked)
+                       if (cdir.fdir)
                                add_untracked(untracked, path.buf + baselen);
                        break;