Merge branch 'en/dir-traversal'
"git clean" and "git ls-files -i" had confusion around working on or showing ignored paths inside an ignored directory, which has been corrected. * en/dir-traversal: dir: introduce readdir_skip_dot_and_dotdot() helper dir: update stale description of treat_directory() dir: traverse into untracked directories if they may have ignored subfiles dir: avoid unnecessary traversal into ignored directory t3001, t7300: add testcase showcasing missed directory traversal t7300: add testcase showing unnecessary traversal into ignored directory ls-files: error out on -i unless -o or -c are specified dir: report number of visited directories and paths with trace2 dir: convert trace calls to trace2 equivalents
This commit is contained in:
commit
33be431c0c
@ -189,10 +189,8 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
|
||||
strbuf_complete(path, '/');
|
||||
|
||||
len = path->len;
|
||||
while ((e = readdir(dir)) != NULL) {
|
||||
while ((e = readdir_skip_dot_and_dotdot(dir)) != NULL) {
|
||||
struct stat st;
|
||||
if (is_dot_or_dotdot(e->d_name))
|
||||
continue;
|
||||
|
||||
strbuf_setlen(path, len);
|
||||
strbuf_addstr(path, e->d_name);
|
||||
|
@ -752,6 +752,9 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
|
||||
if (pathspec.nr && error_unmatch)
|
||||
ps_matched = xcalloc(pathspec.nr, 1);
|
||||
|
||||
if ((dir.flags & DIR_SHOW_IGNORED) && !show_others && !show_cached)
|
||||
die("ls-files -i must be used with either -o or -c");
|
||||
|
||||
if ((dir.flags & DIR_SHOW_IGNORED) && !exc_given)
|
||||
die("ls-files --ignored needs some exclude pattern");
|
||||
|
||||
|
@ -118,10 +118,8 @@ static void prune_worktrees(void)
|
||||
struct dirent *d;
|
||||
if (!dir)
|
||||
return;
|
||||
while ((d = readdir(dir)) != NULL) {
|
||||
while ((d = readdir_skip_dot_and_dotdot(dir)) != NULL) {
|
||||
char *path;
|
||||
if (is_dot_or_dotdot(d->d_name))
|
||||
continue;
|
||||
strbuf_reset(&reason);
|
||||
if (should_prune_worktree(d->d_name, &reason, &path, expire))
|
||||
prune_worktree(d->d_name, reason.buf);
|
||||
|
@ -26,9 +26,8 @@ static int read_directory_contents(const char *path, struct string_list *list)
|
||||
if (!(dir = opendir(path)))
|
||||
return error("Could not open directory %s", path);
|
||||
|
||||
while ((e = readdir(dir)))
|
||||
if (!is_dot_or_dotdot(e->d_name))
|
||||
string_list_insert(list, e->d_name);
|
||||
while ((e = readdir_skip_dot_and_dotdot(dir)))
|
||||
string_list_insert(list, e->d_name);
|
||||
|
||||
closedir(dir);
|
||||
return 0;
|
||||
|
146
dir.c
146
dir.c
@ -59,6 +59,18 @@ void dir_init(struct dir_struct *dir)
|
||||
memset(dir, 0, sizeof(*dir));
|
||||
}
|
||||
|
||||
struct dirent *
|
||||
readdir_skip_dot_and_dotdot(DIR *dirp)
|
||||
{
|
||||
struct dirent *e;
|
||||
|
||||
while ((e = readdir(dirp)) != NULL) {
|
||||
if (!is_dot_or_dotdot(e->d_name))
|
||||
break;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
int count_slashes(const char *s)
|
||||
{
|
||||
int cnt = 0;
|
||||
@ -1749,13 +1761,13 @@ static enum exist_status directory_exists_in_index(struct index_state *istate,
|
||||
* 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
|
||||
* (a) if DIR_SHOW_OTHER_DIRECTORIES flag is set, we show it as
|
||||
* just a directory, unless DIR_HIDE_EMPTY_DIRECTORIES is
|
||||
* also true, in which case we need to check if it contains any
|
||||
* untracked and / or ignored files.
|
||||
* (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.
|
||||
* (b) if it looks like a git directory and we don't have the
|
||||
* DIR_NO_GITLINKS flag, then we treat it as a gitlink, and
|
||||
* show it as a directory.
|
||||
* (c) otherwise, we recurse into it.
|
||||
*/
|
||||
static enum path_treatment treat_directory(struct dir_struct *dir,
|
||||
@ -1843,7 +1855,7 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
|
||||
return path_recurse;
|
||||
}
|
||||
|
||||
/* This is the "show_other_directories" case */
|
||||
assert(dir->flags & DIR_SHOW_OTHER_DIRECTORIES);
|
||||
|
||||
/*
|
||||
* If we have a pathspec which could match something _below_ this
|
||||
@ -1854,27 +1866,42 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
|
||||
if (matches_how == MATCHED_RECURSIVELY_LEADING_PATHSPEC)
|
||||
return path_recurse;
|
||||
|
||||
/* Special cases for where this directory is excluded/ignored */
|
||||
if (excluded) {
|
||||
/*
|
||||
* If DIR_SHOW_OTHER_DIRECTORIES is set and we're not
|
||||
* hiding empty directories, there is no need to
|
||||
* recurse into an ignored directory.
|
||||
*/
|
||||
if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
|
||||
return path_excluded;
|
||||
|
||||
/*
|
||||
* Even if we are hiding empty directories, we can still avoid
|
||||
* recursing into ignored directories for DIR_SHOW_IGNORED_TOO
|
||||
* if DIR_SHOW_IGNORED_TOO_MODE_MATCHING is also set.
|
||||
*/
|
||||
if ((dir->flags & DIR_SHOW_IGNORED_TOO) &&
|
||||
(dir->flags & DIR_SHOW_IGNORED_TOO_MODE_MATCHING))
|
||||
return path_excluded;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 entries below)
|
||||
* Other than the path_recurse case above, we only need to
|
||||
* recurse into untracked directories if any of the following
|
||||
* bits is set:
|
||||
* - DIR_SHOW_IGNORED (because then we need to determine if
|
||||
* there are ignored entries below)
|
||||
* - DIR_SHOW_IGNORED_TOO (same as above)
|
||||
* - DIR_HIDE_EMPTY_DIRECTORIES (because we have to determine if
|
||||
* the directory is empty)
|
||||
*/
|
||||
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 (!excluded &&
|
||||
!(dir->flags & (DIR_SHOW_IGNORED |
|
||||
DIR_SHOW_IGNORED_TOO |
|
||||
DIR_HIDE_EMPTY_DIRECTORIES))) {
|
||||
return path_untracked;
|
||||
}
|
||||
|
||||
/*
|
||||
* Even if we don't want to know all the paths under an untracked or
|
||||
@ -2326,7 +2353,7 @@ static int read_cached_dir(struct cached_dir *cdir)
|
||||
struct dirent *de;
|
||||
|
||||
if (cdir->fdir) {
|
||||
de = readdir(cdir->fdir);
|
||||
de = readdir_skip_dot_and_dotdot(cdir->fdir);
|
||||
if (!de) {
|
||||
cdir->d_name = NULL;
|
||||
cdir->d_type = DT_UNKNOWN;
|
||||
@ -2440,6 +2467,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
|
||||
|
||||
if (open_cached_dir(&cdir, dir, untracked, istate, &path, check_only))
|
||||
goto out;
|
||||
dir->visited_directories++;
|
||||
|
||||
if (untracked)
|
||||
untracked->check_only = !!check_only;
|
||||
@ -2448,6 +2476,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
|
||||
/* check how the file or directory should be treated */
|
||||
state = treat_path(dir, untracked, &cdir, istate, &path,
|
||||
baselen, pathspec);
|
||||
dir->visited_paths++;
|
||||
|
||||
if (state > dir_state)
|
||||
dir_state = state;
|
||||
@ -2760,15 +2789,53 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d
|
||||
return root;
|
||||
}
|
||||
|
||||
static void emit_traversal_statistics(struct dir_struct *dir,
|
||||
struct repository *repo,
|
||||
const char *path,
|
||||
int path_len)
|
||||
{
|
||||
if (!trace2_is_enabled())
|
||||
return;
|
||||
|
||||
if (!path_len) {
|
||||
trace2_data_string("read_directory", repo, "path", "");
|
||||
} else {
|
||||
struct strbuf tmp = STRBUF_INIT;
|
||||
strbuf_add(&tmp, path, path_len);
|
||||
trace2_data_string("read_directory", repo, "path", tmp.buf);
|
||||
strbuf_release(&tmp);
|
||||
}
|
||||
|
||||
trace2_data_intmax("read_directory", repo,
|
||||
"directories-visited", dir->visited_directories);
|
||||
trace2_data_intmax("read_directory", repo,
|
||||
"paths-visited", dir->visited_paths);
|
||||
|
||||
if (!dir->untracked)
|
||||
return;
|
||||
trace2_data_intmax("read_directory", repo,
|
||||
"node-creation", dir->untracked->dir_created);
|
||||
trace2_data_intmax("read_directory", repo,
|
||||
"gitignore-invalidation",
|
||||
dir->untracked->gitignore_invalidated);
|
||||
trace2_data_intmax("read_directory", repo,
|
||||
"directory-invalidation",
|
||||
dir->untracked->dir_invalidated);
|
||||
trace2_data_intmax("read_directory", repo,
|
||||
"opendir", dir->untracked->dir_opened);
|
||||
}
|
||||
|
||||
int read_directory(struct dir_struct *dir, struct index_state *istate,
|
||||
const char *path, int len, const struct pathspec *pathspec)
|
||||
{
|
||||
struct untracked_cache_dir *untracked;
|
||||
|
||||
trace_performance_enter();
|
||||
trace2_region_enter("dir", "read_directory", istate->repo);
|
||||
dir->visited_paths = 0;
|
||||
dir->visited_directories = 0;
|
||||
|
||||
if (has_symlink_leading_path(path, len)) {
|
||||
trace_performance_leave("read directory %.*s", len, path);
|
||||
trace2_region_leave("dir", "read_directory", istate->repo);
|
||||
return dir->nr;
|
||||
}
|
||||
|
||||
@ -2784,23 +2851,15 @@ 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);
|
||||
|
||||
trace_performance_leave("read directory %.*s", len, path);
|
||||
emit_traversal_statistics(dir, istate->repo, path, len);
|
||||
|
||||
trace2_region_leave("dir", "read_directory", istate->repo);
|
||||
if (dir->untracked) {
|
||||
static int force_untracked_cache = -1;
|
||||
static struct trace_key trace_untracked_stats = TRACE_KEY_INIT(UNTRACKED_STATS);
|
||||
|
||||
if (force_untracked_cache < 0)
|
||||
force_untracked_cache =
|
||||
git_env_bool("GIT_FORCE_UNTRACKED_CACHE", 0);
|
||||
trace_printf_key(&trace_untracked_stats,
|
||||
"node creation: %u\n"
|
||||
"gitignore invalidation: %u\n"
|
||||
"directory invalidation: %u\n"
|
||||
"opendir: %u\n",
|
||||
dir->untracked->dir_created,
|
||||
dir->untracked->gitignore_invalidated,
|
||||
dir->untracked->dir_invalidated,
|
||||
dir->untracked->dir_opened);
|
||||
if (force_untracked_cache &&
|
||||
dir->untracked == istate->untracked &&
|
||||
(dir->untracked->dir_opened ||
|
||||
@ -2811,6 +2870,7 @@ int read_directory(struct dir_struct *dir, struct index_state *istate,
|
||||
FREE_AND_NULL(dir->untracked);
|
||||
}
|
||||
}
|
||||
|
||||
return dir->nr;
|
||||
}
|
||||
|
||||
@ -2892,11 +2952,9 @@ int is_empty_dir(const char *path)
|
||||
if (!dir)
|
||||
return 0;
|
||||
|
||||
while ((e = readdir(dir)) != NULL)
|
||||
if (!is_dot_or_dotdot(e->d_name)) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
e = readdir_skip_dot_and_dotdot(dir);
|
||||
if (e)
|
||||
ret = 0;
|
||||
|
||||
closedir(dir);
|
||||
return ret;
|
||||
@ -2936,10 +2994,8 @@ static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)
|
||||
strbuf_complete(path, '/');
|
||||
|
||||
len = path->len;
|
||||
while ((e = readdir(dir)) != NULL) {
|
||||
while ((e = readdir_skip_dot_and_dotdot(dir)) != NULL) {
|
||||
struct stat st;
|
||||
if (is_dot_or_dotdot(e->d_name))
|
||||
continue;
|
||||
|
||||
strbuf_setlen(path, len);
|
||||
strbuf_addstr(path, e->d_name);
|
||||
|
6
dir.h
6
dir.h
@ -336,8 +336,14 @@ struct dir_struct {
|
||||
struct oid_stat ss_info_exclude;
|
||||
struct oid_stat ss_excludes_file;
|
||||
unsigned unmanaged_exclude_files;
|
||||
|
||||
/* Stats about the traversal */
|
||||
unsigned visited_paths;
|
||||
unsigned visited_directories;
|
||||
};
|
||||
|
||||
struct dirent *readdir_skip_dot_and_dotdot(DIR *dirp);
|
||||
|
||||
/*Count the number of slashes for string s*/
|
||||
int count_slashes(const char *s);
|
||||
|
||||
|
5
entry.c
5
entry.c
@ -58,12 +58,9 @@ static void remove_subtree(struct strbuf *path)
|
||||
|
||||
if (!dir)
|
||||
die_errno("cannot opendir '%s'", path->buf);
|
||||
while ((de = readdir(dir)) != NULL) {
|
||||
while ((de = readdir_skip_dot_and_dotdot(dir)) != NULL) {
|
||||
struct stat st;
|
||||
|
||||
if (is_dot_or_dotdot(de->d_name))
|
||||
continue;
|
||||
|
||||
strbuf_addch(path, '/');
|
||||
strbuf_addstr(path, de->d_name);
|
||||
if (lstat(path->buf, &st))
|
||||
|
@ -695,13 +695,10 @@ int notes_merge_commit(struct notes_merge_options *o,
|
||||
|
||||
strbuf_addch(&path, '/');
|
||||
baselen = path.len;
|
||||
while ((e = readdir(dir)) != NULL) {
|
||||
while ((e = readdir_skip_dot_and_dotdot(dir)) != NULL) {
|
||||
struct stat st;
|
||||
struct object_id obj_oid, blob_oid;
|
||||
|
||||
if (is_dot_or_dotdot(e->d_name))
|
||||
continue;
|
||||
|
||||
if (get_oid_hex(e->d_name, &obj_oid)) {
|
||||
if (o->verbosity >= 3)
|
||||
printf("Skipping non-SHA1 entry '%s%s'\n",
|
||||
|
@ -2350,10 +2350,8 @@ int for_each_file_in_obj_subdir(unsigned int subdir_nr,
|
||||
strbuf_addch(path, '/');
|
||||
baselen = path->len;
|
||||
|
||||
while ((de = readdir(dir))) {
|
||||
while ((de = readdir_skip_dot_and_dotdot(dir))) {
|
||||
size_t namelen;
|
||||
if (is_dot_or_dotdot(de->d_name))
|
||||
continue;
|
||||
|
||||
namelen = strlen(de->d_name);
|
||||
strbuf_setlen(path, baselen);
|
||||
|
@ -813,10 +813,7 @@ void for_each_file_in_pack_dir(const char *objdir,
|
||||
}
|
||||
strbuf_addch(&path, '/');
|
||||
dirnamelen = path.len;
|
||||
while ((de = readdir(dir)) != NULL) {
|
||||
if (is_dot_or_dotdot(de->d_name))
|
||||
continue;
|
||||
|
||||
while ((de = readdir_skip_dot_and_dotdot(dir)) != NULL) {
|
||||
strbuf_setlen(&path, dirnamelen);
|
||||
strbuf_addstr(&path, de->d_name);
|
||||
|
||||
|
4
rerere.c
4
rerere.c
@ -1190,13 +1190,11 @@ void rerere_gc(struct repository *r, struct string_list *rr)
|
||||
if (!dir)
|
||||
die_errno(_("unable to open rr-cache directory"));
|
||||
/* Collect stale conflict IDs ... */
|
||||
while ((e = readdir(dir))) {
|
||||
while ((e = readdir_skip_dot_and_dotdot(dir))) {
|
||||
struct rerere_dir *rr_dir;
|
||||
struct rerere_id id;
|
||||
int now_empty;
|
||||
|
||||
if (is_dot_or_dotdot(e->d_name))
|
||||
continue;
|
||||
if (!is_rr_cache_dirname(e->d_name))
|
||||
continue; /* or should we remove e->d_name? */
|
||||
|
||||
|
@ -116,7 +116,7 @@ test_expect_success 'Exclusion in a non-XDG global ignore file' '
|
||||
test_expect_success 'Checking XDG ignore file when HOME is unset' '
|
||||
(sane_unset HOME &&
|
||||
git config --unset core.excludesfile &&
|
||||
git ls-files --exclude-standard --ignored >actual) &&
|
||||
git ls-files --exclude-standard --ignored --others >actual) &&
|
||||
test_must_be_empty actual
|
||||
'
|
||||
|
||||
|
@ -292,6 +292,11 @@ EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'ls-files with "**" patterns and --directory' '
|
||||
# Expectation same as previous test
|
||||
git ls-files --directory -o -i --exclude "**/a.1" >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'ls-files with "**" patterns and no slashes' '
|
||||
git ls-files -o -i --exclude "one**a.1" >actual &&
|
||||
|
@ -29,11 +29,11 @@ test_expect_success 'add file to gitignore' '
|
||||
'
|
||||
check_all_output
|
||||
|
||||
test_expect_success 'ls-files -i lists only tracked-but-ignored files' '
|
||||
test_expect_success 'ls-files -i -c lists only tracked-but-ignored files' '
|
||||
echo content >other-file &&
|
||||
git add other-file &&
|
||||
echo file >expect &&
|
||||
git ls-files -i --exclude-standard >output &&
|
||||
git ls-files -i -c --exclude-standard >output &&
|
||||
test_cmp expect output
|
||||
'
|
||||
|
||||
|
@ -57,6 +57,20 @@ iuc () {
|
||||
return $ret
|
||||
}
|
||||
|
||||
get_relevant_traces () {
|
||||
# From the GIT_TRACE2_PERF data of the form
|
||||
# $TIME $FILE:$LINE | d0 | main | data | r1 | ? | ? | read_directo | $RELEVANT_STAT
|
||||
# extract the $RELEVANT_STAT fields. We don't care about region_enter
|
||||
# or region_leave, or stats for things outside read_directory.
|
||||
INPUT_FILE=$1
|
||||
OUTPUT_FILE=$2
|
||||
grep data.*read_directo $INPUT_FILE |
|
||||
cut -d "|" -f 9 |
|
||||
grep -v visited \
|
||||
>"$OUTPUT_FILE"
|
||||
}
|
||||
|
||||
|
||||
test_lazy_prereq UNTRACKED_CACHE '
|
||||
{ git update-index --test-untracked-cache; ret=$?; } &&
|
||||
test $ret -ne 1
|
||||
@ -129,19 +143,21 @@ EOF
|
||||
|
||||
test_expect_success 'status first time (empty cache)' '
|
||||
avoid_racy &&
|
||||
: >../trace &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
: >../trace.output &&
|
||||
GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
|
||||
git status --porcelain >../actual &&
|
||||
iuc status --porcelain >../status.iuc &&
|
||||
test_cmp ../status.expect ../status.iuc &&
|
||||
test_cmp ../status.expect ../actual &&
|
||||
get_relevant_traces ../trace.output ../trace.relevant &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 3
|
||||
gitignore invalidation: 1
|
||||
directory invalidation: 0
|
||||
opendir: 4
|
||||
....path:
|
||||
....node-creation:3
|
||||
....gitignore-invalidation:1
|
||||
....directory-invalidation:0
|
||||
....opendir:4
|
||||
EOF
|
||||
test_cmp ../trace.expect ../trace
|
||||
test_cmp ../trace.expect ../trace.relevant
|
||||
'
|
||||
|
||||
test_expect_success 'untracked cache after first status' '
|
||||
@ -151,19 +167,21 @@ test_expect_success 'untracked cache after first status' '
|
||||
|
||||
test_expect_success 'status second time (fully populated cache)' '
|
||||
avoid_racy &&
|
||||
: >../trace &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
: >../trace.output &&
|
||||
GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
|
||||
git status --porcelain >../actual &&
|
||||
iuc status --porcelain >../status.iuc &&
|
||||
test_cmp ../status.expect ../status.iuc &&
|
||||
test_cmp ../status.expect ../actual &&
|
||||
get_relevant_traces ../trace.output ../trace.relevant &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 0
|
||||
gitignore invalidation: 0
|
||||
directory invalidation: 0
|
||||
opendir: 0
|
||||
....path:
|
||||
....node-creation:0
|
||||
....gitignore-invalidation:0
|
||||
....directory-invalidation:0
|
||||
....opendir:0
|
||||
EOF
|
||||
test_cmp ../trace.expect ../trace
|
||||
test_cmp ../trace.expect ../trace.relevant
|
||||
'
|
||||
|
||||
test_expect_success 'untracked cache after second status' '
|
||||
@ -174,8 +192,8 @@ test_expect_success 'untracked cache after second status' '
|
||||
test_expect_success 'modify in root directory, one dir invalidation' '
|
||||
avoid_racy &&
|
||||
: >four &&
|
||||
: >../trace &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
: >../trace.output &&
|
||||
GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
|
||||
git status --porcelain >../actual &&
|
||||
iuc status --porcelain >../status.iuc &&
|
||||
cat >../status.expect <<EOF &&
|
||||
@ -189,13 +207,15 @@ A two
|
||||
EOF
|
||||
test_cmp ../status.expect ../status.iuc &&
|
||||
test_cmp ../status.expect ../actual &&
|
||||
get_relevant_traces ../trace.output ../trace.relevant &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 0
|
||||
gitignore invalidation: 0
|
||||
directory invalidation: 1
|
||||
opendir: 1
|
||||
....path:
|
||||
....node-creation:0
|
||||
....gitignore-invalidation:0
|
||||
....directory-invalidation:1
|
||||
....opendir:1
|
||||
EOF
|
||||
test_cmp ../trace.expect ../trace
|
||||
test_cmp ../trace.expect ../trace.relevant
|
||||
|
||||
'
|
||||
|
||||
@ -223,8 +243,8 @@ EOF
|
||||
test_expect_success 'new .gitignore invalidates recursively' '
|
||||
avoid_racy &&
|
||||
echo four >.gitignore &&
|
||||
: >../trace &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
: >../trace.output &&
|
||||
GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
|
||||
git status --porcelain >../actual &&
|
||||
iuc status --porcelain >../status.iuc &&
|
||||
cat >../status.expect <<EOF &&
|
||||
@ -238,13 +258,15 @@ A two
|
||||
EOF
|
||||
test_cmp ../status.expect ../status.iuc &&
|
||||
test_cmp ../status.expect ../actual &&
|
||||
get_relevant_traces ../trace.output ../trace.relevant &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 0
|
||||
gitignore invalidation: 1
|
||||
directory invalidation: 1
|
||||
opendir: 4
|
||||
....path:
|
||||
....node-creation:0
|
||||
....gitignore-invalidation:1
|
||||
....directory-invalidation:1
|
||||
....opendir:4
|
||||
EOF
|
||||
test_cmp ../trace.expect ../trace
|
||||
test_cmp ../trace.expect ../trace.relevant
|
||||
|
||||
'
|
||||
|
||||
@ -272,8 +294,8 @@ EOF
|
||||
test_expect_success 'new info/exclude invalidates everything' '
|
||||
avoid_racy &&
|
||||
echo three >>.git/info/exclude &&
|
||||
: >../trace &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
: >../trace.output &&
|
||||
GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
|
||||
git status --porcelain >../actual &&
|
||||
iuc status --porcelain >../status.iuc &&
|
||||
cat >../status.expect <<EOF &&
|
||||
@ -285,13 +307,15 @@ A two
|
||||
EOF
|
||||
test_cmp ../status.expect ../status.iuc &&
|
||||
test_cmp ../status.expect ../actual &&
|
||||
get_relevant_traces ../trace.output ../trace.relevant &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 0
|
||||
gitignore invalidation: 1
|
||||
directory invalidation: 0
|
||||
opendir: 4
|
||||
....path:
|
||||
....node-creation:0
|
||||
....gitignore-invalidation:1
|
||||
....directory-invalidation:0
|
||||
....opendir:4
|
||||
EOF
|
||||
test_cmp ../trace.expect ../trace
|
||||
test_cmp ../trace.expect ../trace.relevant
|
||||
'
|
||||
|
||||
test_expect_success 'verify untracked cache dump' '
|
||||
@ -330,8 +354,8 @@ EOF
|
||||
'
|
||||
|
||||
test_expect_success 'status after the move' '
|
||||
: >../trace &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
: >../trace.output &&
|
||||
GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
|
||||
git status --porcelain >../actual &&
|
||||
iuc status --porcelain >../status.iuc &&
|
||||
cat >../status.expect <<EOF &&
|
||||
@ -343,13 +367,15 @@ A one
|
||||
EOF
|
||||
test_cmp ../status.expect ../status.iuc &&
|
||||
test_cmp ../status.expect ../actual &&
|
||||
get_relevant_traces ../trace.output ../trace.relevant &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 0
|
||||
gitignore invalidation: 0
|
||||
directory invalidation: 0
|
||||
opendir: 1
|
||||
....path:
|
||||
....node-creation:0
|
||||
....gitignore-invalidation:0
|
||||
....directory-invalidation:0
|
||||
....opendir:1
|
||||
EOF
|
||||
test_cmp ../trace.expect ../trace
|
||||
test_cmp ../trace.expect ../trace.relevant
|
||||
'
|
||||
|
||||
test_expect_success 'verify untracked cache dump' '
|
||||
@ -389,8 +415,8 @@ EOF
|
||||
'
|
||||
|
||||
test_expect_success 'status after the move' '
|
||||
: >../trace &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
: >../trace.output &&
|
||||
GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
|
||||
git status --porcelain >../actual &&
|
||||
iuc status --porcelain >../status.iuc &&
|
||||
cat >../status.expect <<EOF &&
|
||||
@ -402,13 +428,15 @@ A two
|
||||
EOF
|
||||
test_cmp ../status.expect ../status.iuc &&
|
||||
test_cmp ../status.expect ../actual &&
|
||||
get_relevant_traces ../trace.output ../trace.relevant &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 0
|
||||
gitignore invalidation: 0
|
||||
directory invalidation: 0
|
||||
opendir: 1
|
||||
....path:
|
||||
....node-creation:0
|
||||
....gitignore-invalidation:0
|
||||
....directory-invalidation:0
|
||||
....opendir:1
|
||||
EOF
|
||||
test_cmp ../trace.expect ../trace
|
||||
test_cmp ../trace.expect ../trace.relevant
|
||||
'
|
||||
|
||||
test_expect_success 'verify untracked cache dump' '
|
||||
@ -438,8 +466,8 @@ test_expect_success 'set up for sparse checkout testing' '
|
||||
'
|
||||
|
||||
test_expect_success 'status after commit' '
|
||||
: >../trace &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
: >../trace.output &&
|
||||
GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
|
||||
git status --porcelain >../actual &&
|
||||
iuc status --porcelain >../status.iuc &&
|
||||
cat >../status.expect <<EOF &&
|
||||
@ -448,13 +476,15 @@ test_expect_success 'status after commit' '
|
||||
EOF
|
||||
test_cmp ../status.expect ../status.iuc &&
|
||||
test_cmp ../status.expect ../actual &&
|
||||
get_relevant_traces ../trace.output ../trace.relevant &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 0
|
||||
gitignore invalidation: 0
|
||||
directory invalidation: 0
|
||||
opendir: 2
|
||||
....path:
|
||||
....node-creation:0
|
||||
....gitignore-invalidation:0
|
||||
....directory-invalidation:0
|
||||
....opendir:2
|
||||
EOF
|
||||
test_cmp ../trace.expect ../trace
|
||||
test_cmp ../trace.expect ../trace.relevant
|
||||
'
|
||||
|
||||
test_expect_success 'untracked cache correct after commit' '
|
||||
@ -496,9 +526,9 @@ test_expect_success 'create/modify files, some of which are gitignored' '
|
||||
'
|
||||
|
||||
test_expect_success 'test sparse status with untracked cache' '
|
||||
: >../trace &&
|
||||
: >../trace.output &&
|
||||
avoid_racy &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
|
||||
git status --porcelain >../status.actual &&
|
||||
iuc status --porcelain >../status.iuc &&
|
||||
cat >../status.expect <<EOF &&
|
||||
@ -509,13 +539,15 @@ test_expect_success 'test sparse status with untracked cache' '
|
||||
EOF
|
||||
test_cmp ../status.expect ../status.iuc &&
|
||||
test_cmp ../status.expect ../status.actual &&
|
||||
get_relevant_traces ../trace.output ../trace.relevant &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 0
|
||||
gitignore invalidation: 1
|
||||
directory invalidation: 2
|
||||
opendir: 2
|
||||
....path:
|
||||
....node-creation:0
|
||||
....gitignore-invalidation:1
|
||||
....directory-invalidation:2
|
||||
....opendir:2
|
||||
EOF
|
||||
test_cmp ../trace.expect ../trace
|
||||
test_cmp ../trace.expect ../trace.relevant
|
||||
'
|
||||
|
||||
test_expect_success 'untracked cache correct after status' '
|
||||
@ -539,8 +571,8 @@ EOF
|
||||
|
||||
test_expect_success 'test sparse status again with untracked cache' '
|
||||
avoid_racy &&
|
||||
: >../trace &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
: >../trace.output &&
|
||||
GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
|
||||
git status --porcelain >../status.actual &&
|
||||
iuc status --porcelain >../status.iuc &&
|
||||
cat >../status.expect <<EOF &&
|
||||
@ -551,13 +583,15 @@ test_expect_success 'test sparse status again with untracked cache' '
|
||||
EOF
|
||||
test_cmp ../status.expect ../status.iuc &&
|
||||
test_cmp ../status.expect ../status.actual &&
|
||||
get_relevant_traces ../trace.output ../trace.relevant &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 0
|
||||
gitignore invalidation: 0
|
||||
directory invalidation: 0
|
||||
opendir: 0
|
||||
....path:
|
||||
....node-creation:0
|
||||
....gitignore-invalidation:0
|
||||
....directory-invalidation:0
|
||||
....opendir:0
|
||||
EOF
|
||||
test_cmp ../trace.expect ../trace
|
||||
test_cmp ../trace.expect ../trace.relevant
|
||||
'
|
||||
|
||||
test_expect_success 'set up for test of subdir and sparse checkouts' '
|
||||
@ -568,8 +602,8 @@ test_expect_success 'set up for test of subdir and sparse checkouts' '
|
||||
|
||||
test_expect_success 'test sparse status with untracked cache and subdir' '
|
||||
avoid_racy &&
|
||||
: >../trace &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
: >../trace.output &&
|
||||
GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
|
||||
git status --porcelain >../status.actual &&
|
||||
iuc status --porcelain >../status.iuc &&
|
||||
cat >../status.expect <<EOF &&
|
||||
@ -581,13 +615,15 @@ test_expect_success 'test sparse status with untracked cache and subdir' '
|
||||
EOF
|
||||
test_cmp ../status.expect ../status.iuc &&
|
||||
test_cmp ../status.expect ../status.actual &&
|
||||
get_relevant_traces ../trace.output ../trace.relevant &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 2
|
||||
gitignore invalidation: 0
|
||||
directory invalidation: 1
|
||||
opendir: 3
|
||||
....path:
|
||||
....node-creation:2
|
||||
....gitignore-invalidation:0
|
||||
....directory-invalidation:1
|
||||
....opendir:3
|
||||
EOF
|
||||
test_cmp ../trace.expect ../trace
|
||||
test_cmp ../trace.expect ../trace.relevant
|
||||
'
|
||||
|
||||
test_expect_success 'verify untracked cache dump (sparse/subdirs)' '
|
||||
@ -616,19 +652,21 @@ EOF
|
||||
|
||||
test_expect_success 'test sparse status again with untracked cache and subdir' '
|
||||
avoid_racy &&
|
||||
: >../trace &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \
|
||||
: >../trace.output &&
|
||||
GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
|
||||
git status --porcelain >../status.actual &&
|
||||
iuc status --porcelain >../status.iuc &&
|
||||
test_cmp ../status.expect ../status.iuc &&
|
||||
test_cmp ../status.expect ../status.actual &&
|
||||
get_relevant_traces ../trace.output ../trace.relevant &&
|
||||
cat >../trace.expect <<EOF &&
|
||||
node creation: 0
|
||||
gitignore invalidation: 0
|
||||
directory invalidation: 0
|
||||
opendir: 0
|
||||
....path:
|
||||
....node-creation:0
|
||||
....gitignore-invalidation:0
|
||||
....directory-invalidation:0
|
||||
....opendir:0
|
||||
EOF
|
||||
test_cmp ../trace.expect ../trace
|
||||
test_cmp ../trace.expect ../trace.relevant
|
||||
'
|
||||
|
||||
test_expect_success 'move entry in subdir from untracked to cached' '
|
||||
|
@ -746,4 +746,46 @@ test_expect_success 'clean untracked paths by pathspec' '
|
||||
test_must_be_empty actual
|
||||
'
|
||||
|
||||
test_expect_success 'avoid traversing into ignored directories' '
|
||||
test_when_finished rm -f output error trace.* &&
|
||||
test_create_repo avoid-traversing-deep-hierarchy &&
|
||||
(
|
||||
cd avoid-traversing-deep-hierarchy &&
|
||||
|
||||
mkdir -p untracked/subdir/with/a &&
|
||||
>untracked/subdir/with/a/random-file.txt &&
|
||||
|
||||
GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace.output" \
|
||||
git clean -ffdxn -e untracked
|
||||
) &&
|
||||
|
||||
# Make sure we only visited into the top-level directory, and did
|
||||
# not traverse into the "untracked" subdirectory since it was excluded
|
||||
grep data.*read_directo.*directories-visited trace.output |
|
||||
cut -d "|" -f 9 >trace.relevant &&
|
||||
cat >trace.expect <<-EOF &&
|
||||
..directories-visited:1
|
||||
EOF
|
||||
test_cmp trace.expect trace.relevant
|
||||
'
|
||||
|
||||
test_expect_success 'traverse into directories that may have ignored entries' '
|
||||
test_when_finished rm -f output &&
|
||||
test_create_repo need-to-traverse-into-hierarchy &&
|
||||
(
|
||||
cd need-to-traverse-into-hierarchy &&
|
||||
mkdir -p modules/foobar/src/generated &&
|
||||
> modules/foobar/src/generated/code.c &&
|
||||
> modules/foobar/Makefile &&
|
||||
echo "/modules/**/src/generated/" >.gitignore &&
|
||||
|
||||
git clean -fX modules/foobar >../output &&
|
||||
|
||||
grep Removing ../output &&
|
||||
|
||||
test_path_is_missing modules/foobar/src/generated/code.c &&
|
||||
test_path_is_file modules/foobar/Makefile
|
||||
)
|
||||
'
|
||||
|
||||
test_done
|
||||
|
@ -334,7 +334,7 @@ test_expect_success UNTRACKED_CACHE 'ignore .git changes when invalidating UNTR'
|
||||
git config core.fsmonitor .git/hooks/fsmonitor-test &&
|
||||
git update-index --untracked-cache &&
|
||||
git update-index --fsmonitor &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace-before" \
|
||||
GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace-before" \
|
||||
git status &&
|
||||
test-tool dump-untracked-cache >../before
|
||||
) &&
|
||||
@ -346,12 +346,12 @@ test_expect_success UNTRACKED_CACHE 'ignore .git changes when invalidating UNTR'
|
||||
EOF
|
||||
(
|
||||
cd dot-git &&
|
||||
GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace-after" \
|
||||
GIT_TRACE2_PERF="$TRASH_DIRECTORY/trace-after" \
|
||||
git status &&
|
||||
test-tool dump-untracked-cache >../after
|
||||
) &&
|
||||
grep "directory invalidation" trace-before >>before &&
|
||||
grep "directory invalidation" trace-after >>after &&
|
||||
grep "directory-invalidation" trace-before | cut -d"|" -f 9 >>before &&
|
||||
grep "directory-invalidation" trace-after | cut -d"|" -f 9 >>after &&
|
||||
# UNTR extension unchanged, dir invalidation count unchanged
|
||||
test_cmp before after
|
||||
'
|
||||
|
12
worktree.c
12
worktree.c
@ -128,10 +128,8 @@ struct worktree **get_worktrees(void)
|
||||
dir = opendir(path.buf);
|
||||
strbuf_release(&path);
|
||||
if (dir) {
|
||||
while ((d = readdir(dir)) != NULL) {
|
||||
while ((d = readdir_skip_dot_and_dotdot(dir)) != NULL) {
|
||||
struct worktree *linked = NULL;
|
||||
if (is_dot_or_dotdot(d->d_name))
|
||||
continue;
|
||||
|
||||
if ((linked = get_linked_worktree(d->d_name))) {
|
||||
ALLOC_GROW(list, counter + 1, alloc);
|
||||
@ -486,13 +484,9 @@ int submodule_uses_worktrees(const char *path)
|
||||
if (!dir)
|
||||
return 0;
|
||||
|
||||
while ((d = readdir(dir)) != NULL) {
|
||||
if (is_dot_or_dotdot(d->d_name))
|
||||
continue;
|
||||
|
||||
d = readdir_skip_dot_and_dotdot(dir);
|
||||
if (d != NULL)
|
||||
ret = 1;
|
||||
break;
|
||||
}
|
||||
closedir(dir);
|
||||
return ret;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user