mirror of
https://github.com/git/git.git
synced 2024-11-27 12:03:55 +08:00
Merge branch 'en/fill-directory-exponential'
The directory traversal code had redundant recursive calls which made its performance characteristics exponential with respect to the depth of the tree, which was corrected. * en/fill-directory-exponential: completion: fix 'git add' on paths under an untracked directory Fix error-prone fill_directory() API; make it only return matches dir: replace double pathspec matching with single in treat_directory() dir: include DIR_KEEP_UNTRACKED_CONTENTS handling in treat_directory() dir: replace exponential algorithm with a linear one dir: refactor treat_directory to clarify control flow dir: fix confusion based on variable tense dir: fix broken comment dir: consolidate treat_path() and treat_one_path() dir: fix simple typo in comment t3000: add more testcases testing a variety of ls-files issues t7063: more thorough status checking
This commit is contained in:
commit
6eacc39b6d
@ -983,12 +983,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
|
||||
if (!cache_name_is_other(ent->name, ent->len))
|
||||
continue;
|
||||
|
||||
if (pathspec.nr)
|
||||
matches = dir_path_match(&the_index, ent, &pathspec, 0, NULL);
|
||||
|
||||
if (pathspec.nr && !matches)
|
||||
continue;
|
||||
|
||||
if (lstat(ent->name, &st))
|
||||
die_errno("Cannot lstat '%s'", ent->name);
|
||||
|
||||
|
@ -701,8 +701,6 @@ static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec,
|
||||
|
||||
fill_directory(&dir, opt->repo->index, pathspec);
|
||||
for (i = 0; i < dir.nr; i++) {
|
||||
if (!dir_path_match(opt->repo->index, dir.entries[i], pathspec, 0, NULL))
|
||||
continue;
|
||||
hit |= grep_file(opt, dir.entries[i]->name);
|
||||
if (hit && opt->status_only)
|
||||
break;
|
||||
|
@ -128,8 +128,9 @@ static void show_dir_entry(const struct index_state *istate,
|
||||
if (len > ent->len)
|
||||
die("git ls-files: internal error - directory entry not superset of prefix");
|
||||
|
||||
if (!dir_path_match(istate, ent, &pathspec, len, ps_matched))
|
||||
return;
|
||||
/* If ps_matches is non-NULL, figure out which pathspec(s) match. */
|
||||
if (ps_matched)
|
||||
dir_path_match(istate, ent, &pathspec, len, ps_matched);
|
||||
|
||||
fputs(tag, stdout);
|
||||
write_eolinfo(istate, NULL, ent->name);
|
||||
|
@ -861,30 +861,23 @@ static int get_untracked_files(const struct pathspec *ps, int include_untracked,
|
||||
struct strbuf *untracked_files)
|
||||
{
|
||||
int i;
|
||||
int max_len;
|
||||
int found = 0;
|
||||
char *seen;
|
||||
struct dir_struct dir;
|
||||
|
||||
memset(&dir, 0, sizeof(dir));
|
||||
if (include_untracked != INCLUDE_ALL_FILES)
|
||||
setup_standard_excludes(&dir);
|
||||
|
||||
seen = xcalloc(ps->nr, 1);
|
||||
|
||||
max_len = fill_directory(&dir, the_repository->index, ps);
|
||||
fill_directory(&dir, the_repository->index, ps);
|
||||
for (i = 0; i < dir.nr; i++) {
|
||||
struct dir_entry *ent = dir.entries[i];
|
||||
if (dir_path_match(&the_index, ent, ps, max_len, seen)) {
|
||||
found++;
|
||||
strbuf_addstr(untracked_files, ent->name);
|
||||
/* NUL-terminate: will be fed to update-index -z */
|
||||
strbuf_addch(untracked_files, '\0');
|
||||
}
|
||||
found++;
|
||||
strbuf_addstr(untracked_files, ent->name);
|
||||
/* NUL-terminate: will be fed to update-index -z */
|
||||
strbuf_addch(untracked_files, '\0');
|
||||
free(ent);
|
||||
}
|
||||
|
||||
free(seen);
|
||||
free(dir.entries);
|
||||
free(dir.ignored);
|
||||
clear_directory(&dir);
|
||||
|
@ -504,7 +504,7 @@ __git_index_files ()
|
||||
{
|
||||
local root="$2" match="$3"
|
||||
|
||||
__git_ls_files_helper "$root" "$1" "$match" |
|
||||
__git_ls_files_helper "$root" "$1" "${match:-?}" |
|
||||
awk -F / -v pfx="${2//\\/\\\\}" '{
|
||||
paths[$1] = 1
|
||||
}
|
||||
|
428
dir.c
428
dir.c
@ -1727,36 +1727,59 @@ static enum exist_status directory_exists_in_index(struct index_state *istate,
|
||||
static enum path_treatment treat_directory(struct dir_struct *dir,
|
||||
struct index_state *istate,
|
||||
struct untracked_cache_dir *untracked,
|
||||
const char *dirname, int len, int baselen, int exclude,
|
||||
const char *dirname, int len, int baselen, int excluded,
|
||||
const struct pathspec *pathspec)
|
||||
{
|
||||
int nested_repo = 0;
|
||||
|
||||
/*
|
||||
* WARNING: From this function, you can return path_recurse or you
|
||||
* can call read_directory_recursive() (or neither), but
|
||||
* you CAN'T DO BOTH.
|
||||
*/
|
||||
enum path_treatment state;
|
||||
int matches_how = 0;
|
||||
int nested_repo = 0, check_only, stop_early;
|
||||
int old_ignored_nr, old_untracked_nr;
|
||||
/* The "len-1" is to strip the final '/' */
|
||||
switch (directory_exists_in_index(istate, dirname, len-1)) {
|
||||
case index_directory:
|
||||
enum exist_status status = directory_exists_in_index(istate, dirname, len-1);
|
||||
|
||||
if (status == index_directory)
|
||||
return path_recurse;
|
||||
|
||||
case index_gitdir:
|
||||
if (status == index_gitdir)
|
||||
return path_none;
|
||||
if (status != index_nonexistent)
|
||||
BUG("Unhandled value for directory_exists_in_index: %d\n", status);
|
||||
|
||||
case index_nonexistent:
|
||||
if ((dir->flags & DIR_SKIP_NESTED_GIT) ||
|
||||
!(dir->flags & DIR_NO_GITLINKS)) {
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
strbuf_addstr(&sb, dirname);
|
||||
nested_repo = is_nonbare_repository_dir(&sb);
|
||||
strbuf_release(&sb);
|
||||
}
|
||||
if (nested_repo)
|
||||
return ((dir->flags & DIR_SKIP_NESTED_GIT) ? path_none :
|
||||
(exclude ? path_excluded : path_untracked));
|
||||
/*
|
||||
* We don't want to descend into paths that don't match the necessary
|
||||
* patterns. Clearly, if we don't have a pathspec, then we can't check
|
||||
* for matching patterns. Also, if (excluded) then we know we matched
|
||||
* the exclusion patterns so as an optimization we can skip checking
|
||||
* for matching patterns.
|
||||
*/
|
||||
if (pathspec && !excluded) {
|
||||
matches_how = do_match_pathspec(istate, pathspec, dirname, len,
|
||||
0 /* prefix */, NULL /* seen */,
|
||||
DO_MATCH_LEADING_PATHSPEC);
|
||||
if (!matches_how)
|
||||
return path_none;
|
||||
}
|
||||
|
||||
if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
|
||||
break;
|
||||
if (exclude &&
|
||||
(dir->flags & DIR_SHOW_IGNORED_TOO) &&
|
||||
(dir->flags & DIR_SHOW_IGNORED_TOO_MODE_MATCHING)) {
|
||||
|
||||
if ((dir->flags & DIR_SKIP_NESTED_GIT) ||
|
||||
!(dir->flags & DIR_NO_GITLINKS)) {
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
strbuf_addstr(&sb, dirname);
|
||||
nested_repo = is_nonbare_repository_dir(&sb);
|
||||
strbuf_release(&sb);
|
||||
}
|
||||
if (nested_repo)
|
||||
return ((dir->flags & DIR_SKIP_NESTED_GIT) ? path_none :
|
||||
(excluded ? path_excluded : path_untracked));
|
||||
|
||||
if (!(dir->flags & DIR_SHOW_OTHER_DIRECTORIES)) {
|
||||
if (excluded &&
|
||||
(dir->flags & DIR_SHOW_IGNORED_TOO) &&
|
||||
(dir->flags & DIR_SHOW_IGNORED_TOO_MODE_MATCHING)) {
|
||||
|
||||
/*
|
||||
* This is an excluded directory and we are
|
||||
@ -1783,18 +1806,134 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
|
||||
|
||||
/* This is the "show_other_directories" case */
|
||||
|
||||
if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
|
||||
return exclude ? path_excluded : path_untracked;
|
||||
|
||||
untracked = lookup_untracked(dir->untracked, untracked,
|
||||
dirname + baselen, len - baselen);
|
||||
/*
|
||||
* If we have a pathspec which could match something _below_ this
|
||||
* directory (e.g. when checking 'subdir/' having a pathspec like
|
||||
* 'subdir/some/deep/path/file' or 'subdir/widget-*.c'), then we
|
||||
* need to recurse.
|
||||
*/
|
||||
if (matches_how == MATCHED_RECURSIVELY_LEADING_PATHSPEC)
|
||||
return path_recurse;
|
||||
|
||||
/*
|
||||
* If this is an excluded directory, then we only need to check if
|
||||
* the directory contains any files.
|
||||
* Other than the path_recurse case immediately above, we only need
|
||||
* to recurse into untracked/ignored directories if either of the
|
||||
* following bits is set:
|
||||
* - DIR_SHOW_IGNORED_TOO (because then we need to determine if
|
||||
* there are ignored directories below)
|
||||
* - DIR_HIDE_EMPTY_DIRECTORIES (because we have to determine if
|
||||
* the directory is empty)
|
||||
*/
|
||||
return read_directory_recursive(dir, istate, dirname, len,
|
||||
untracked, 1, exclude, pathspec);
|
||||
if (!(dir->flags & (DIR_SHOW_IGNORED_TOO | DIR_HIDE_EMPTY_DIRECTORIES)))
|
||||
return excluded ? path_excluded : path_untracked;
|
||||
|
||||
/*
|
||||
* ...and even if DIR_SHOW_IGNORED_TOO is set, we can still avoid
|
||||
* recursing into ignored directories if the path is excluded and
|
||||
* DIR_SHOW_IGNORED_TOO_MODE_MATCHING is also set.
|
||||
*/
|
||||
if (excluded &&
|
||||
(dir->flags & DIR_SHOW_IGNORED_TOO) &&
|
||||
(dir->flags & DIR_SHOW_IGNORED_TOO_MODE_MATCHING))
|
||||
return path_excluded;
|
||||
|
||||
/*
|
||||
* If we have we don't want to know the all the paths under an
|
||||
* untracked or ignored directory, we still need to go into the
|
||||
* directory to determine if it is empty (because an empty directory
|
||||
* should be path_none instead of path_excluded or path_untracked).
|
||||
*/
|
||||
check_only = ((dir->flags & DIR_HIDE_EMPTY_DIRECTORIES) &&
|
||||
!(dir->flags & DIR_SHOW_IGNORED_TOO));
|
||||
|
||||
/*
|
||||
* However, there's another optimization possible as a subset of
|
||||
* check_only, based on the cases we have to consider:
|
||||
* A) Directory matches no exclude patterns:
|
||||
* * Directory is empty => path_none
|
||||
* * Directory has an untracked file under it => path_untracked
|
||||
* * Directory has only ignored files under it => path_excluded
|
||||
* B) Directory matches an exclude pattern:
|
||||
* * Directory is empty => path_none
|
||||
* * Directory has an untracked file under it => path_excluded
|
||||
* * Directory has only ignored files under it => path_excluded
|
||||
* In case A, we can exit as soon as we've found an untracked
|
||||
* file but otherwise have to walk all files. In case B, though,
|
||||
* we can stop at the first file we find under the directory.
|
||||
*/
|
||||
stop_early = check_only && excluded;
|
||||
|
||||
/*
|
||||
* If /every/ file within an untracked directory is ignored, then
|
||||
* we want to treat the directory as ignored (for e.g. status
|
||||
* --porcelain), without listing the individual ignored files
|
||||
* underneath. To do so, we'll save the current ignored_nr, and
|
||||
* pop all the ones added after it if it turns out the entire
|
||||
* directory is ignored. Also, when DIR_SHOW_IGNORED_TOO and
|
||||
* !DIR_KEEP_UNTRACKED_CONTENTS then we don't want to show
|
||||
* untracked paths so will need to pop all those off the last
|
||||
* after we traverse.
|
||||
*/
|
||||
old_ignored_nr = dir->ignored_nr;
|
||||
old_untracked_nr = dir->nr;
|
||||
|
||||
/* Actually recurse into dirname now, we'll fixup the state later. */
|
||||
untracked = lookup_untracked(dir->untracked, untracked,
|
||||
dirname + baselen, len - baselen);
|
||||
state = read_directory_recursive(dir, istate, dirname, len, untracked,
|
||||
check_only, stop_early, pathspec);
|
||||
|
||||
/* There are a variety of reasons we may need to fixup the state... */
|
||||
if (state == path_excluded) {
|
||||
/* state == path_excluded implies all paths under
|
||||
* dirname were ignored...
|
||||
*
|
||||
* if running e.g. `git status --porcelain --ignored=matching`,
|
||||
* then we want to see the subpaths that are ignored.
|
||||
*
|
||||
* if running e.g. just `git status --porcelain`, then
|
||||
* we just want the directory itself to be listed as ignored
|
||||
* and not the individual paths underneath.
|
||||
*/
|
||||
int want_ignored_subpaths =
|
||||
((dir->flags & DIR_SHOW_IGNORED_TOO) &&
|
||||
(dir->flags & DIR_SHOW_IGNORED_TOO_MODE_MATCHING));
|
||||
|
||||
if (want_ignored_subpaths) {
|
||||
/*
|
||||
* with --ignored=matching, we want the subpaths
|
||||
* INSTEAD of the directory itself.
|
||||
*/
|
||||
state = path_none;
|
||||
} else {
|
||||
int i;
|
||||
for (i = old_ignored_nr + 1; i<dir->ignored_nr; ++i)
|
||||
FREE_AND_NULL(dir->ignored[i]);
|
||||
dir->ignored_nr = old_ignored_nr;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We may need to ignore some of the untracked paths we found while
|
||||
* traversing subdirectories.
|
||||
*/
|
||||
if ((dir->flags & DIR_SHOW_IGNORED_TOO) &&
|
||||
!(dir->flags & DIR_KEEP_UNTRACKED_CONTENTS)) {
|
||||
int i;
|
||||
for (i = old_untracked_nr + 1; i<dir->nr; ++i)
|
||||
FREE_AND_NULL(dir->entries[i]);
|
||||
dir->nr = old_untracked_nr;
|
||||
}
|
||||
|
||||
/*
|
||||
* If there is nothing under the current directory and we are not
|
||||
* hiding empty directories, then we need to report on the
|
||||
* untracked or ignored status of the directory itself.
|
||||
*/
|
||||
if (state == path_none && !(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
|
||||
state = excluded ? path_excluded : path_untracked;
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1934,85 +2073,6 @@ static int resolve_dtype(int dtype, struct index_state *istate,
|
||||
return dtype;
|
||||
}
|
||||
|
||||
static enum path_treatment treat_one_path(struct dir_struct *dir,
|
||||
struct untracked_cache_dir *untracked,
|
||||
struct index_state *istate,
|
||||
struct strbuf *path,
|
||||
int baselen,
|
||||
const struct pathspec *pathspec,
|
||||
int dtype)
|
||||
{
|
||||
int exclude;
|
||||
int has_path_in_index = !!index_file_exists(istate, path->buf, path->len, ignore_case);
|
||||
enum path_treatment path_treatment;
|
||||
|
||||
dtype = resolve_dtype(dtype, istate, path->buf, path->len);
|
||||
|
||||
/* Always exclude indexed files */
|
||||
if (dtype != DT_DIR && has_path_in_index)
|
||||
return path_none;
|
||||
|
||||
/*
|
||||
* When we are looking at a directory P in the working tree,
|
||||
* there are three cases:
|
||||
*
|
||||
* (1) P exists in the index. Everything inside the directory P in
|
||||
* the working tree needs to go when P is checked out from the
|
||||
* index.
|
||||
*
|
||||
* (2) P does not exist in the index, but there is P/Q in the index.
|
||||
* We know P will stay a directory when we check out the contents
|
||||
* of the index, but we do not know yet if there is a directory
|
||||
* P/Q in the working tree to be killed, so we need to recurse.
|
||||
*
|
||||
* (3) P does not exist in the index, and there is no P/Q in the index
|
||||
* to require P to be a directory, either. Only in this case, we
|
||||
* know that everything inside P will not be killed without
|
||||
* recursing.
|
||||
*/
|
||||
if ((dir->flags & DIR_COLLECT_KILLED_ONLY) &&
|
||||
(dtype == DT_DIR) &&
|
||||
!has_path_in_index &&
|
||||
(directory_exists_in_index(istate, path->buf, path->len) == index_nonexistent))
|
||||
return path_none;
|
||||
|
||||
exclude = is_excluded(dir, istate, path->buf, &dtype);
|
||||
|
||||
/*
|
||||
* Excluded? If we don't explicitly want to show
|
||||
* ignored files, ignore it
|
||||
*/
|
||||
if (exclude && !(dir->flags & (DIR_SHOW_IGNORED|DIR_SHOW_IGNORED_TOO)))
|
||||
return path_excluded;
|
||||
|
||||
switch (dtype) {
|
||||
default:
|
||||
return path_none;
|
||||
case DT_DIR:
|
||||
strbuf_addch(path, '/');
|
||||
path_treatment = treat_directory(dir, istate, untracked,
|
||||
path->buf, path->len,
|
||||
baselen, exclude, pathspec);
|
||||
/*
|
||||
* If 1) we only want to return directories that
|
||||
* match an exclude pattern and 2) this directory does
|
||||
* not match an exclude pattern but all of its
|
||||
* contents are excluded, then indicate that we should
|
||||
* recurse into this directory (instead of marking the
|
||||
* directory itself as an ignored path).
|
||||
*/
|
||||
if (!exclude &&
|
||||
path_treatment == path_excluded &&
|
||||
(dir->flags & DIR_SHOW_IGNORED_TOO) &&
|
||||
(dir->flags & DIR_SHOW_IGNORED_TOO_MODE_MATCHING))
|
||||
return path_recurse;
|
||||
return path_treatment;
|
||||
case DT_REG:
|
||||
case DT_LNK:
|
||||
return exclude ? path_excluded : path_untracked;
|
||||
}
|
||||
}
|
||||
|
||||
static enum path_treatment treat_path_fast(struct dir_struct *dir,
|
||||
struct untracked_cache_dir *untracked,
|
||||
struct cached_dir *cdir,
|
||||
@ -2021,6 +2081,11 @@ static enum path_treatment treat_path_fast(struct dir_struct *dir,
|
||||
int baselen,
|
||||
const struct pathspec *pathspec)
|
||||
{
|
||||
/*
|
||||
* WARNING: From this function, you can return path_recurse or you
|
||||
* can call read_directory_recursive() (or neither), but
|
||||
* you CAN'T DO BOTH.
|
||||
*/
|
||||
strbuf_setlen(path, baselen);
|
||||
if (!cdir->ucd) {
|
||||
strbuf_addstr(path, cdir->file);
|
||||
@ -2054,6 +2119,8 @@ static enum path_treatment treat_path(struct dir_struct *dir,
|
||||
int baselen,
|
||||
const struct pathspec *pathspec)
|
||||
{
|
||||
int has_path_in_index, dtype, excluded;
|
||||
|
||||
if (!cdir->d_name)
|
||||
return treat_path_fast(dir, untracked, cdir, istate, path,
|
||||
baselen, pathspec);
|
||||
@ -2064,8 +2131,72 @@ static enum path_treatment treat_path(struct dir_struct *dir,
|
||||
if (simplify_away(path->buf, path->len, pathspec))
|
||||
return path_none;
|
||||
|
||||
return treat_one_path(dir, untracked, istate, path, baselen, pathspec,
|
||||
cdir->d_type);
|
||||
dtype = resolve_dtype(cdir->d_type, istate, path->buf, path->len);
|
||||
|
||||
/* Always exclude indexed files */
|
||||
has_path_in_index = !!index_file_exists(istate, path->buf, path->len,
|
||||
ignore_case);
|
||||
if (dtype != DT_DIR && has_path_in_index)
|
||||
return path_none;
|
||||
|
||||
/*
|
||||
* When we are looking at a directory P in the working tree,
|
||||
* there are three cases:
|
||||
*
|
||||
* (1) P exists in the index. Everything inside the directory P in
|
||||
* the working tree needs to go when P is checked out from the
|
||||
* index.
|
||||
*
|
||||
* (2) P does not exist in the index, but there is P/Q in the index.
|
||||
* We know P will stay a directory when we check out the contents
|
||||
* of the index, but we do not know yet if there is a directory
|
||||
* P/Q in the working tree to be killed, so we need to recurse.
|
||||
*
|
||||
* (3) P does not exist in the index, and there is no P/Q in the index
|
||||
* to require P to be a directory, either. Only in this case, we
|
||||
* know that everything inside P will not be killed without
|
||||
* recursing.
|
||||
*/
|
||||
if ((dir->flags & DIR_COLLECT_KILLED_ONLY) &&
|
||||
(dtype == DT_DIR) &&
|
||||
!has_path_in_index &&
|
||||
(directory_exists_in_index(istate, path->buf, path->len) == index_nonexistent))
|
||||
return path_none;
|
||||
|
||||
excluded = is_excluded(dir, istate, path->buf, &dtype);
|
||||
|
||||
/*
|
||||
* Excluded? If we don't explicitly want to show
|
||||
* ignored files, ignore it
|
||||
*/
|
||||
if (excluded && !(dir->flags & (DIR_SHOW_IGNORED|DIR_SHOW_IGNORED_TOO)))
|
||||
return path_excluded;
|
||||
|
||||
switch (dtype) {
|
||||
default:
|
||||
return path_none;
|
||||
case DT_DIR:
|
||||
/*
|
||||
* WARNING: Do not ignore/amend the return value from
|
||||
* treat_directory(), and especially do not change it to return
|
||||
* path_recurse as that can cause exponential slowdown.
|
||||
* Instead, modify treat_directory() to return the right value.
|
||||
*/
|
||||
strbuf_addch(path, '/');
|
||||
return treat_directory(dir, istate, untracked,
|
||||
path->buf, path->len,
|
||||
baselen, excluded, pathspec);
|
||||
case DT_REG:
|
||||
case DT_LNK:
|
||||
if (excluded)
|
||||
return path_excluded;
|
||||
if (pathspec &&
|
||||
!do_match_pathspec(istate, pathspec, path->buf, path->len,
|
||||
0 /* prefix */, NULL /* seen */,
|
||||
0 /* flags */))
|
||||
return path_none;
|
||||
return path_untracked;
|
||||
}
|
||||
}
|
||||
|
||||
static void add_untracked(struct untracked_cache_dir *dir, const char *name)
|
||||
@ -2245,7 +2376,7 @@ static void add_path_to_appropriate_result_list(struct dir_struct *dir,
|
||||
* 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.
|
||||
* whether an untracked or excluded 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
|
||||
@ -2258,14 +2389,10 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
|
||||
int stop_at_first_file, const struct pathspec *pathspec)
|
||||
{
|
||||
/*
|
||||
* WARNING WARNING WARNING:
|
||||
*
|
||||
* Any updates to the traversal logic here may need corresponding
|
||||
* updates in treat_leading_path(). See the commit message for the
|
||||
* commit adding this warning as well as the commit preceding it
|
||||
* for details.
|
||||
* WARNING: Do NOT recurse unless path_recurse is returned from
|
||||
* treat_path(). Recursing on any other return value
|
||||
* can result in exponential slowdown.
|
||||
*/
|
||||
|
||||
struct cached_dir cdir;
|
||||
enum path_treatment state, subdir_state, dir_state = path_none;
|
||||
struct strbuf path = STRBUF_INIT;
|
||||
@ -2287,13 +2414,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
|
||||
dir_state = state;
|
||||
|
||||
/* recurse into subdir if instructed by treat_path */
|
||||
if ((state == path_recurse) ||
|
||||
((state == path_untracked) &&
|
||||
(resolve_dtype(cdir.d_type, istate, path.buf, path.len) == DT_DIR) &&
|
||||
((dir->flags & DIR_SHOW_IGNORED_TOO) ||
|
||||
(pathspec &&
|
||||
do_match_pathspec(istate, pathspec, path.buf, path.len,
|
||||
baselen, NULL, DO_MATCH_LEADING_PATHSPEC) == MATCHED_RECURSIVELY_LEADING_PATHSPEC)))) {
|
||||
if (state == path_recurse) {
|
||||
struct untracked_cache_dir *ud;
|
||||
ud = lookup_untracked(dir->untracked, untracked,
|
||||
path.buf + baselen,
|
||||
@ -2341,7 +2462,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
|
||||
add_untracked(untracked, path.buf + baselen);
|
||||
break;
|
||||
}
|
||||
/* skip the dir_add_* part */
|
||||
/* skip the add_path_to_appropriate_result_list() */
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -2377,15 +2498,6 @@ static int treat_leading_path(struct dir_struct *dir,
|
||||
const char *path, int len,
|
||||
const struct pathspec *pathspec)
|
||||
{
|
||||
/*
|
||||
* WARNING WARNING WARNING:
|
||||
*
|
||||
* Any updates to the traversal logic here may need corresponding
|
||||
* updates in read_directory_recursive(). See 777b420347 (dir:
|
||||
* synchronize treat_leading_path() and read_directory_recursive(),
|
||||
* 2019-12-19) and its parent commit for details.
|
||||
*/
|
||||
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
struct strbuf subdir = STRBUF_INIT;
|
||||
int prevlen, baselen;
|
||||
@ -2436,23 +2548,7 @@ static int treat_leading_path(struct dir_struct *dir,
|
||||
strbuf_reset(&subdir);
|
||||
strbuf_add(&subdir, path+prevlen, baselen-prevlen);
|
||||
cdir.d_name = subdir.buf;
|
||||
state = treat_path(dir, NULL, &cdir, istate, &sb, prevlen,
|
||||
pathspec);
|
||||
if (state == path_untracked &&
|
||||
resolve_dtype(cdir.d_type, istate, sb.buf, sb.len) == DT_DIR &&
|
||||
(dir->flags & DIR_SHOW_IGNORED_TOO ||
|
||||
do_match_pathspec(istate, pathspec, sb.buf, sb.len,
|
||||
baselen, NULL, DO_MATCH_LEADING_PATHSPEC) == MATCHED_RECURSIVELY_LEADING_PATHSPEC)) {
|
||||
if (!match_pathspec(istate, pathspec, sb.buf, sb.len,
|
||||
0 /* prefix */, NULL,
|
||||
0 /* do NOT special case dirs */))
|
||||
state = path_none;
|
||||
add_path_to_appropriate_result_list(dir, NULL, &cdir,
|
||||
istate,
|
||||
&sb, baselen,
|
||||
pathspec, state);
|
||||
state = path_recurse;
|
||||
}
|
||||
state = treat_path(dir, NULL, &cdir, istate, &sb, prevlen, pathspec);
|
||||
|
||||
if (state != path_recurse)
|
||||
break; /* do not recurse into it */
|
||||
@ -2652,28 +2748,6 @@ int read_directory(struct dir_struct *dir, struct index_state *istate,
|
||||
QSORT(dir->entries, dir->nr, cmp_dir_entry);
|
||||
QSORT(dir->ignored, dir->ignored_nr, cmp_dir_entry);
|
||||
|
||||
/*
|
||||
* If DIR_SHOW_IGNORED_TOO is set, read_directory_recursive() will
|
||||
* also pick up untracked contents of untracked dirs; by default
|
||||
* we discard these, but given DIR_KEEP_UNTRACKED_CONTENTS we do not.
|
||||
*/
|
||||
if ((dir->flags & DIR_SHOW_IGNORED_TOO) &&
|
||||
!(dir->flags & DIR_KEEP_UNTRACKED_CONTENTS)) {
|
||||
int i, j;
|
||||
|
||||
/* remove from dir->entries untracked contents of untracked dirs */
|
||||
for (i = j = 0; j < dir->nr; j++) {
|
||||
if (i &&
|
||||
check_dir_entry_contains(dir->entries[i - 1], dir->entries[j])) {
|
||||
FREE_AND_NULL(dir->entries[j]);
|
||||
} else {
|
||||
dir->entries[i++] = dir->entries[j];
|
||||
}
|
||||
}
|
||||
|
||||
dir->nr = i;
|
||||
}
|
||||
|
||||
trace_performance_leave("read directory %.*s", len, path);
|
||||
if (dir->untracked) {
|
||||
static int force_untracked_cache = -1;
|
||||
|
@ -91,4 +91,125 @@ test_expect_success SYMLINKS 'ls-files --others with symlinked submodule' '
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'setup nested pathspec search' '
|
||||
test_create_repo nested &&
|
||||
(
|
||||
cd nested &&
|
||||
|
||||
mkdir -p partially_tracked/untracked_dir &&
|
||||
> partially_tracked/content &&
|
||||
> partially_tracked/untracked_dir/file &&
|
||||
|
||||
mkdir -p untracked/deep &&
|
||||
> untracked/deep/path &&
|
||||
> untracked/deep/foo.c &&
|
||||
|
||||
git add partially_tracked/content
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'ls-files -o --directory with single deep dir pathspec' '
|
||||
(
|
||||
cd nested &&
|
||||
|
||||
git ls-files -o --directory untracked/deep/ >actual &&
|
||||
|
||||
cat <<-EOF >expect &&
|
||||
untracked/deep/
|
||||
EOF
|
||||
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'ls-files -o --directory with multiple dir pathspecs' '
|
||||
(
|
||||
cd nested &&
|
||||
|
||||
git ls-files -o --directory partially_tracked/ untracked/ >actual &&
|
||||
|
||||
cat <<-EOF >expect &&
|
||||
partially_tracked/untracked_dir/
|
||||
untracked/
|
||||
EOF
|
||||
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'ls-files -o --directory with mix dir/file pathspecs' '
|
||||
(
|
||||
cd nested &&
|
||||
|
||||
git ls-files -o --directory partially_tracked/ untracked/deep/path >actual &&
|
||||
|
||||
cat <<-EOF >expect &&
|
||||
partially_tracked/untracked_dir/
|
||||
untracked/deep/path
|
||||
EOF
|
||||
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'ls-files --o --directory with glob filetype match' '
|
||||
(
|
||||
cd nested &&
|
||||
|
||||
# globs kinda defeat --directory, but only for that pathspec
|
||||
git ls-files --others --directory partially_tracked "untracked/*.c" >actual &&
|
||||
|
||||
cat <<-EOF >expect &&
|
||||
partially_tracked/untracked_dir/
|
||||
untracked/deep/foo.c
|
||||
EOF
|
||||
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'ls-files --o --directory with mix of tracked states' '
|
||||
(
|
||||
cd nested &&
|
||||
|
||||
# globs kinda defeat --directory, but only for that pathspec
|
||||
git ls-files --others --directory partially_tracked/ "untracked/?*" >actual &&
|
||||
|
||||
cat <<-EOF >expect &&
|
||||
partially_tracked/untracked_dir/
|
||||
untracked/deep/
|
||||
EOF
|
||||
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'ls-files --o --directory with glob filetype match only' '
|
||||
(
|
||||
cd nested &&
|
||||
|
||||
git ls-files --others --directory "untracked/*.c" >actual &&
|
||||
|
||||
cat <<-EOF >expect &&
|
||||
untracked/deep/foo.c
|
||||
EOF
|
||||
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
||||
test_expect_success 'ls-files --o --directory to get immediate paths under one dir only' '
|
||||
(
|
||||
cd nested &&
|
||||
|
||||
git ls-files --others --directory "untracked/?*" >actual &&
|
||||
|
||||
cat <<-EOF >expect &&
|
||||
untracked/deep/
|
||||
EOF
|
||||
|
||||
test_cmp expect actual
|
||||
)
|
||||
'
|
||||
|
||||
test_done
|
||||
|
@ -30,6 +30,30 @@ status_is_clean() {
|
||||
test_must_be_empty ../status.actual
|
||||
}
|
||||
|
||||
# Ignore_Untracked_Cache, abbreviated to 3 letters because then people can
|
||||
# compare commands side-by-side, e.g.
|
||||
# iuc status --porcelain >expect &&
|
||||
# git status --porcelain >actual &&
|
||||
# test_cmp expect actual
|
||||
iuc () {
|
||||
git ls-files -s >../current-index-entries
|
||||
git ls-files -t | sed -ne s/^S.//p >../current-sparse-entries
|
||||
|
||||
GIT_INDEX_FILE=.git/tmp_index
|
||||
export GIT_INDEX_FILE
|
||||
git update-index --index-info <../current-index-entries
|
||||
git update-index --skip-worktree $(cat ../current-sparse-entries)
|
||||
|
||||
git -c core.untrackedCache=false "$@"
|
||||
ret=$?
|
||||
|
||||
rm ../current-index-entries
|
||||
rm $GIT_INDEX_FILE
|
||||
unset GIT_INDEX_FILE
|
||||
|
||||
return $ret
|
||||
}
|
||||
|
||||
test_lazy_prereq UNTRACKED_CACHE '
|
||||
{ git update-index --test-untracked-cache; ret=$?; } &&
|
||||
test $ret -ne 1
|
||||
@ -95,6 +119,8 @@ test_expect_success 'status first time (empty cache)' '
|
||||
: >../trace &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
git status --porcelain >../actual &&
|
||||
iuc status --porcelain >../status.iuc &&
|
||||
test_cmp ../status.expect ../status.iuc &&
|
||||
test_cmp ../status.expect ../actual &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 3
|
||||
@ -115,6 +141,8 @@ test_expect_success 'status second time (fully populated cache)' '
|
||||
: >../trace &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
git status --porcelain >../actual &&
|
||||
iuc status --porcelain >../status.iuc &&
|
||||
test_cmp ../status.expect ../status.iuc &&
|
||||
test_cmp ../status.expect ../actual &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 0
|
||||
@ -136,6 +164,7 @@ test_expect_success 'modify in root directory, one dir invalidation' '
|
||||
: >../trace &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
git status --porcelain >../actual &&
|
||||
iuc status --porcelain >../status.iuc &&
|
||||
cat >../status.expect <<EOF &&
|
||||
A done/one
|
||||
A one
|
||||
@ -145,6 +174,7 @@ A two
|
||||
?? four
|
||||
?? three
|
||||
EOF
|
||||
test_cmp ../status.expect ../status.iuc &&
|
||||
test_cmp ../status.expect ../actual &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 0
|
||||
@ -183,6 +213,7 @@ test_expect_success 'new .gitignore invalidates recursively' '
|
||||
: >../trace &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
git status --porcelain >../actual &&
|
||||
iuc status --porcelain >../status.iuc &&
|
||||
cat >../status.expect <<EOF &&
|
||||
A done/one
|
||||
A one
|
||||
@ -192,6 +223,7 @@ A two
|
||||
?? dtwo/
|
||||
?? three
|
||||
EOF
|
||||
test_cmp ../status.expect ../status.iuc &&
|
||||
test_cmp ../status.expect ../actual &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 0
|
||||
@ -230,6 +262,7 @@ test_expect_success 'new info/exclude invalidates everything' '
|
||||
: >../trace &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
git status --porcelain >../actual &&
|
||||
iuc status --porcelain >../status.iuc &&
|
||||
cat >../status.expect <<EOF &&
|
||||
A done/one
|
||||
A one
|
||||
@ -237,6 +270,7 @@ A two
|
||||
?? .gitignore
|
||||
?? dtwo/
|
||||
EOF
|
||||
test_cmp ../status.expect ../status.iuc &&
|
||||
test_cmp ../status.expect ../actual &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 0
|
||||
@ -286,6 +320,7 @@ test_expect_success 'status after the move' '
|
||||
: >../trace &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
git status --porcelain >../actual &&
|
||||
iuc status --porcelain >../status.iuc &&
|
||||
cat >../status.expect <<EOF &&
|
||||
A done/one
|
||||
A one
|
||||
@ -293,6 +328,7 @@ A one
|
||||
?? dtwo/
|
||||
?? two
|
||||
EOF
|
||||
test_cmp ../status.expect ../status.iuc &&
|
||||
test_cmp ../status.expect ../actual &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 0
|
||||
@ -343,6 +379,7 @@ test_expect_success 'status after the move' '
|
||||
: >../trace &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
git status --porcelain >../actual &&
|
||||
iuc status --porcelain >../status.iuc &&
|
||||
cat >../status.expect <<EOF &&
|
||||
A done/one
|
||||
A one
|
||||
@ -350,6 +387,7 @@ A two
|
||||
?? .gitignore
|
||||
?? dtwo/
|
||||
EOF
|
||||
test_cmp ../status.expect ../status.iuc &&
|
||||
test_cmp ../status.expect ../actual &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 0
|
||||
@ -390,10 +428,12 @@ test_expect_success 'status after commit' '
|
||||
: >../trace &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
git status --porcelain >../actual &&
|
||||
iuc status --porcelain >../status.iuc &&
|
||||
cat >../status.expect <<EOF &&
|
||||
?? .gitignore
|
||||
?? dtwo/
|
||||
EOF
|
||||
test_cmp ../status.expect ../status.iuc &&
|
||||
test_cmp ../status.expect ../actual &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 0
|
||||
@ -447,12 +487,14 @@ test_expect_success 'test sparse status with untracked cache' '
|
||||
avoid_racy &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
git status --porcelain >../status.actual &&
|
||||
iuc status --porcelain >../status.iuc &&
|
||||
cat >../status.expect <<EOF &&
|
||||
M done/two
|
||||
?? .gitignore
|
||||
?? done/five
|
||||
?? dtwo/
|
||||
EOF
|
||||
test_cmp ../status.expect ../status.iuc &&
|
||||
test_cmp ../status.expect ../status.actual &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 0
|
||||
@ -487,12 +529,14 @@ test_expect_success 'test sparse status again with untracked cache' '
|
||||
: >../trace &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
git status --porcelain >../status.actual &&
|
||||
iuc status --porcelain >../status.iuc &&
|
||||
cat >../status.expect <<EOF &&
|
||||
M done/two
|
||||
?? .gitignore
|
||||
?? done/five
|
||||
?? dtwo/
|
||||
EOF
|
||||
test_cmp ../status.expect ../status.iuc &&
|
||||
test_cmp ../status.expect ../status.actual &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 0
|
||||
@ -514,6 +558,7 @@ test_expect_success 'test sparse status with untracked cache and subdir' '
|
||||
: >../trace &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
git status --porcelain >../status.actual &&
|
||||
iuc status --porcelain >../status.iuc &&
|
||||
cat >../status.expect <<EOF &&
|
||||
M done/two
|
||||
?? .gitignore
|
||||
@ -521,6 +566,7 @@ test_expect_success 'test sparse status with untracked cache and subdir' '
|
||||
?? done/sub/
|
||||
?? dtwo/
|
||||
EOF
|
||||
test_cmp ../status.expect ../status.iuc &&
|
||||
test_cmp ../status.expect ../status.actual &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 2
|
||||
@ -560,6 +606,8 @@ test_expect_success 'test sparse status again with untracked cache and subdir' '
|
||||
: >../trace &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
git status --porcelain >../status.actual &&
|
||||
iuc status --porcelain >../status.iuc &&
|
||||
test_cmp ../status.expect ../status.iuc &&
|
||||
test_cmp ../status.expect ../status.actual &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 0
|
||||
@ -573,6 +621,7 @@ EOF
|
||||
test_expect_success 'move entry in subdir from untracked to cached' '
|
||||
git add dtwo/two &&
|
||||
git status --porcelain >../status.actual &&
|
||||
iuc status --porcelain >../status.iuc &&
|
||||
cat >../status.expect <<EOF &&
|
||||
M done/two
|
||||
A dtwo/two
|
||||
@ -580,12 +629,14 @@ A dtwo/two
|
||||
?? done/five
|
||||
?? done/sub/
|
||||
EOF
|
||||
test_cmp ../status.expect ../status.iuc &&
|
||||
test_cmp ../status.expect ../status.actual
|
||||
'
|
||||
|
||||
test_expect_success 'move entry in subdir from cached to untracked' '
|
||||
git rm --cached dtwo/two &&
|
||||
git status --porcelain >../status.actual &&
|
||||
iuc status --porcelain >../status.iuc &&
|
||||
cat >../status.expect <<EOF &&
|
||||
M done/two
|
||||
?? .gitignore
|
||||
@ -593,6 +644,7 @@ test_expect_success 'move entry in subdir from cached to untracked' '
|
||||
?? done/sub/
|
||||
?? dtwo/
|
||||
EOF
|
||||
test_cmp ../status.expect ../status.iuc &&
|
||||
test_cmp ../status.expect ../status.actual
|
||||
'
|
||||
|
||||
|
@ -1638,6 +1638,11 @@ test_expect_success 'complete files' '
|
||||
echo modify > modified &&
|
||||
test_completion "git add " "modified" &&
|
||||
|
||||
mkdir -p some/deep &&
|
||||
touch some/deep/path &&
|
||||
test_completion "git add some/" "some/deep" &&
|
||||
git clean -f some &&
|
||||
|
||||
touch untracked &&
|
||||
|
||||
: TODO .gitignore should not be here &&
|
||||
|
@ -722,16 +722,14 @@ static void wt_status_collect_untracked(struct wt_status *s)
|
||||
|
||||
for (i = 0; i < dir.nr; i++) {
|
||||
struct dir_entry *ent = dir.entries[i];
|
||||
if (index_name_is_other(istate, ent->name, ent->len) &&
|
||||
dir_path_match(istate, ent, &s->pathspec, 0, NULL))
|
||||
if (index_name_is_other(istate, ent->name, ent->len))
|
||||
string_list_insert(&s->untracked, ent->name);
|
||||
free(ent);
|
||||
}
|
||||
|
||||
for (i = 0; i < dir.ignored_nr; i++) {
|
||||
struct dir_entry *ent = dir.ignored[i];
|
||||
if (index_name_is_other(istate, ent->name, ent->len) &&
|
||||
dir_path_match(istate, ent, &s->pathspec, 0, NULL))
|
||||
if (index_name_is_other(istate, ent->name, ent->len))
|
||||
string_list_insert(&s->ignored, ent->name);
|
||||
free(ent);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user