Merge branch 'jm/status-ignored-directory-optim'

"git status --ignored", when noticing that a directory without any
tracked path is ignored, still enumerated all the ignored paths in
the directory, which is unnecessary.  The codepath has been
optimized to avoid this overhead.

* jm/status-ignored-directory-optim:
  Improve performance of git status --ignored
This commit is contained in:
Junio C Hamano 2017-09-29 11:23:40 +09:00
commit 075bc9c798

47
dir.c
View File

@ -49,7 +49,7 @@ struct cached_dir {
static enum path_treatment read_directory_recursive(struct dir_struct *dir,
struct index_state *istate, const char *path, int len,
struct untracked_cache_dir *untracked,
int check_only, const struct pathspec *pathspec);
int check_only, int stop_at_first_file, const struct pathspec *pathspec);
static int get_dtype(struct dirent *de, struct index_state *istate,
const char *path, int len);
@ -1404,8 +1404,13 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
untracked = lookup_untracked(dir->untracked, untracked,
dirname + baselen, len - baselen);
/*
* If this is an excluded directory, then we only need to check if
* the directory contains any files.
*/
return read_directory_recursive(dir, istate, dirname, len,
untracked, 1, pathspec);
untracked, 1, exclude, pathspec);
}
/*
@ -1633,7 +1638,7 @@ static enum path_treatment treat_path_fast(struct dir_struct *dir,
* with check_only set.
*/
return read_directory_recursive(dir, istate, path->buf, path->len,
cdir->ucd, 1, pathspec);
cdir->ucd, 1, 0, pathspec);
/*
* We get path_recurse in the first run when
* directory_exists_in_index() returns index_nonexistent. We
@ -1793,12 +1798,20 @@ static void close_cached_dir(struct cached_dir *cdir)
* Also, we ignore the name ".git" (even if it is not a directory).
* That likely will not change.
*
* If 'stop_at_first_file' is specified, 'path_excluded' is returned
* to signal that a file was found. This is the least significant value that
* indicates that a file was encountered that does not depend on the order of
* whether an untracked or exluded path was encountered first.
*
* Returns the most significant path_treatment value encountered in the scan.
* If 'stop_at_first_file' is specified, `path_excluded` is the most
* significant path_treatment value that will be returned.
*/
static enum path_treatment read_directory_recursive(struct dir_struct *dir,
struct index_state *istate, const char *base, int baselen,
struct untracked_cache_dir *untracked, int check_only,
const struct pathspec *pathspec)
int stop_at_first_file, const struct pathspec *pathspec)
{
struct cached_dir cdir;
enum path_treatment state, subdir_state, dir_state = path_none;
@ -1832,12 +1845,34 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
subdir_state =
read_directory_recursive(dir, istate, path.buf,
path.len, ud,
check_only, pathspec);
check_only, stop_at_first_file, pathspec);
if (subdir_state > dir_state)
dir_state = subdir_state;
}
if (check_only) {
if (stop_at_first_file) {
/*
* If stopping at first file, then
* signal that a file was found by
* returning `path_excluded`. This is
* to return a consistent value
* regardless of whether an ignored or
* excluded file happened to be
* encountered 1st.
*
* In current usage, the
* `stop_at_first_file` is passed when
* an ancestor directory has matched
* an exclude pattern, so any found
* files will be excluded.
*/
if (dir_state >= path_excluded) {
dir_state = path_excluded;
break;
}
}
/* abort early if maximum state has been reached */
if (dir_state == path_untracked) {
if (cdir.fdir)
@ -2108,7 +2143,7 @@ int read_directory(struct dir_struct *dir, struct index_state *istate,
*/
dir->untracked = NULL;
if (!len || treat_leading_path(dir, istate, path, len, pathspec))
read_directory_recursive(dir, istate, path, len, untracked, 0, pathspec);
read_directory_recursive(dir, istate, path, len, untracked, 0, 0, pathspec);
QSORT(dir->entries, dir->nr, cmp_dir_entry);
QSORT(dir->ignored, dir->ignored_nr, cmp_dir_entry);