diff --git a/dir.c b/dir.c index 86bf6e9311..5415374105 100644 --- 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; diff --git a/dir.h b/dir.h index 1d7a9585fe..ff3d99bcb0 100644 --- a/dir.h +++ b/dir.h @@ -135,6 +135,8 @@ struct untracked_cache { /* Statistics */ int dir_created; int gitignore_invalidated; + int dir_invalidated; + int dir_opened; }; struct dir_struct {