Skip to content

Commit 33be431

Browse files
committed
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
2 parents 2e2ed74 + b548f0f commit 33be431

18 files changed

+298
-172
lines changed

builtin/clean.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,10 +189,8 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
189189
strbuf_complete(path, '/');
190190

191191
len = path->len;
192-
while ((e = readdir(dir)) != NULL) {
192+
while ((e = readdir_skip_dot_and_dotdot(dir)) != NULL) {
193193
struct stat st;
194-
if (is_dot_or_dotdot(e->d_name))
195-
continue;
196194

197195
strbuf_setlen(path, len);
198196
strbuf_addstr(path, e->d_name);

builtin/ls-files.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,9 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
752752
if (pathspec.nr && error_unmatch)
753753
ps_matched = xcalloc(pathspec.nr, 1);
754754

755+
if ((dir.flags & DIR_SHOW_IGNORED) && !show_others && !show_cached)
756+
die("ls-files -i must be used with either -o or -c");
757+
755758
if ((dir.flags & DIR_SHOW_IGNORED) && !exc_given)
756759
die("ls-files --ignored needs some exclude pattern");
757760

builtin/worktree.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,8 @@ static void prune_worktrees(void)
118118
struct dirent *d;
119119
if (!dir)
120120
return;
121-
while ((d = readdir(dir)) != NULL) {
121+
while ((d = readdir_skip_dot_and_dotdot(dir)) != NULL) {
122122
char *path;
123-
if (is_dot_or_dotdot(d->d_name))
124-
continue;
125123
strbuf_reset(&reason);
126124
if (should_prune_worktree(d->d_name, &reason, &path, expire))
127125
prune_worktree(d->d_name, reason.buf);

diff-no-index.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,8 @@ static int read_directory_contents(const char *path, struct string_list *list)
2626
if (!(dir = opendir(path)))
2727
return error("Could not open directory %s", path);
2828

29-
while ((e = readdir(dir)))
30-
if (!is_dot_or_dotdot(e->d_name))
31-
string_list_insert(list, e->d_name);
29+
while ((e = readdir_skip_dot_and_dotdot(dir)))
30+
string_list_insert(list, e->d_name);
3231

3332
closedir(dir);
3433
return 0;

dir.c

Lines changed: 101 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,18 @@ void dir_init(struct dir_struct *dir)
5959
memset(dir, 0, sizeof(*dir));
6060
}
6161

62+
struct dirent *
63+
readdir_skip_dot_and_dotdot(DIR *dirp)
64+
{
65+
struct dirent *e;
66+
67+
while ((e = readdir(dirp)) != NULL) {
68+
if (!is_dot_or_dotdot(e->d_name))
69+
break;
70+
}
71+
return e;
72+
}
73+
6274
int count_slashes(const char *s)
6375
{
6476
int cnt = 0;
@@ -1749,13 +1761,13 @@ static enum exist_status directory_exists_in_index(struct index_state *istate,
17491761
* Case 3: if we didn't have it in the index previously, we
17501762
* have a few sub-cases:
17511763
*
1752-
* (a) if "show_other_directories" is true, we show it as
1753-
* just a directory, unless "hide_empty_directories" is
1764+
* (a) if DIR_SHOW_OTHER_DIRECTORIES flag is set, we show it as
1765+
* just a directory, unless DIR_HIDE_EMPTY_DIRECTORIES is
17541766
* also true, in which case we need to check if it contains any
17551767
* untracked and / or ignored files.
1756-
* (b) if it looks like a git directory, and we don't have
1757-
* 'no_gitlinks' set we treat it as a gitlink, and show it
1758-
* as a directory.
1768+
* (b) if it looks like a git directory and we don't have the
1769+
* DIR_NO_GITLINKS flag, then we treat it as a gitlink, and
1770+
* show it as a directory.
17591771
* (c) otherwise, we recurse into it.
17601772
*/
17611773
static enum path_treatment treat_directory(struct dir_struct *dir,
@@ -1843,7 +1855,7 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
18431855
return path_recurse;
18441856
}
18451857

1846-
/* This is the "show_other_directories" case */
1858+
assert(dir->flags & DIR_SHOW_OTHER_DIRECTORIES);
18471859

18481860
/*
18491861
* 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,
18541866
if (matches_how == MATCHED_RECURSIVELY_LEADING_PATHSPEC)
18551867
return path_recurse;
18561868

1869+
/* Special cases for where this directory is excluded/ignored */
1870+
if (excluded) {
1871+
/*
1872+
* If DIR_SHOW_OTHER_DIRECTORIES is set and we're not
1873+
* hiding empty directories, there is no need to
1874+
* recurse into an ignored directory.
1875+
*/
1876+
if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
1877+
return path_excluded;
1878+
1879+
/*
1880+
* Even if we are hiding empty directories, we can still avoid
1881+
* recursing into ignored directories for DIR_SHOW_IGNORED_TOO
1882+
* if DIR_SHOW_IGNORED_TOO_MODE_MATCHING is also set.
1883+
*/
1884+
if ((dir->flags & DIR_SHOW_IGNORED_TOO) &&
1885+
(dir->flags & DIR_SHOW_IGNORED_TOO_MODE_MATCHING))
1886+
return path_excluded;
1887+
}
1888+
18571889
/*
1858-
* Other than the path_recurse case immediately above, we only need
1859-
* to recurse into untracked/ignored directories if either of the
1860-
* following bits is set:
1861-
* - DIR_SHOW_IGNORED_TOO (because then we need to determine if
1862-
* there are ignored entries below)
1890+
* Other than the path_recurse case above, we only need to
1891+
* recurse into untracked directories if any of the following
1892+
* bits is set:
1893+
* - DIR_SHOW_IGNORED (because then we need to determine if
1894+
* there are ignored entries below)
1895+
* - DIR_SHOW_IGNORED_TOO (same as above)
18631896
* - DIR_HIDE_EMPTY_DIRECTORIES (because we have to determine if
18641897
* the directory is empty)
18651898
*/
1866-
if (!(dir->flags & (DIR_SHOW_IGNORED_TOO | DIR_HIDE_EMPTY_DIRECTORIES)))
1867-
return excluded ? path_excluded : path_untracked;
1868-
1869-
/*
1870-
* ...and even if DIR_SHOW_IGNORED_TOO is set, we can still avoid
1871-
* recursing into ignored directories if the path is excluded and
1872-
* DIR_SHOW_IGNORED_TOO_MODE_MATCHING is also set.
1873-
*/
1874-
if (excluded &&
1875-
(dir->flags & DIR_SHOW_IGNORED_TOO) &&
1876-
(dir->flags & DIR_SHOW_IGNORED_TOO_MODE_MATCHING))
1877-
return path_excluded;
1899+
if (!excluded &&
1900+
!(dir->flags & (DIR_SHOW_IGNORED |
1901+
DIR_SHOW_IGNORED_TOO |
1902+
DIR_HIDE_EMPTY_DIRECTORIES))) {
1903+
return path_untracked;
1904+
}
18781905

18791906
/*
18801907
* 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)
23262353
struct dirent *de;
23272354

23282355
if (cdir->fdir) {
2329-
de = readdir(cdir->fdir);
2356+
de = readdir_skip_dot_and_dotdot(cdir->fdir);
23302357
if (!de) {
23312358
cdir->d_name = NULL;
23322359
cdir->d_type = DT_UNKNOWN;
@@ -2440,6 +2467,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
24402467

24412468
if (open_cached_dir(&cdir, dir, untracked, istate, &path, check_only))
24422469
goto out;
2470+
dir->visited_directories++;
24432471

24442472
if (untracked)
24452473
untracked->check_only = !!check_only;
@@ -2448,6 +2476,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
24482476
/* check how the file or directory should be treated */
24492477
state = treat_path(dir, untracked, &cdir, istate, &path,
24502478
baselen, pathspec);
2479+
dir->visited_paths++;
24512480

24522481
if (state > dir_state)
24532482
dir_state = state;
@@ -2760,15 +2789,53 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d
27602789
return root;
27612790
}
27622791

2792+
static void emit_traversal_statistics(struct dir_struct *dir,
2793+
struct repository *repo,
2794+
const char *path,
2795+
int path_len)
2796+
{
2797+
if (!trace2_is_enabled())
2798+
return;
2799+
2800+
if (!path_len) {
2801+
trace2_data_string("read_directory", repo, "path", "");
2802+
} else {
2803+
struct strbuf tmp = STRBUF_INIT;
2804+
strbuf_add(&tmp, path, path_len);
2805+
trace2_data_string("read_directory", repo, "path", tmp.buf);
2806+
strbuf_release(&tmp);
2807+
}
2808+
2809+
trace2_data_intmax("read_directory", repo,
2810+
"directories-visited", dir->visited_directories);
2811+
trace2_data_intmax("read_directory", repo,
2812+
"paths-visited", dir->visited_paths);
2813+
2814+
if (!dir->untracked)
2815+
return;
2816+
trace2_data_intmax("read_directory", repo,
2817+
"node-creation", dir->untracked->dir_created);
2818+
trace2_data_intmax("read_directory", repo,
2819+
"gitignore-invalidation",
2820+
dir->untracked->gitignore_invalidated);
2821+
trace2_data_intmax("read_directory", repo,
2822+
"directory-invalidation",
2823+
dir->untracked->dir_invalidated);
2824+
trace2_data_intmax("read_directory", repo,
2825+
"opendir", dir->untracked->dir_opened);
2826+
}
2827+
27632828
int read_directory(struct dir_struct *dir, struct index_state *istate,
27642829
const char *path, int len, const struct pathspec *pathspec)
27652830
{
27662831
struct untracked_cache_dir *untracked;
27672832

2768-
trace_performance_enter();
2833+
trace2_region_enter("dir", "read_directory", istate->repo);
2834+
dir->visited_paths = 0;
2835+
dir->visited_directories = 0;
27692836

27702837
if (has_symlink_leading_path(path, len)) {
2771-
trace_performance_leave("read directory %.*s", len, path);
2838+
trace2_region_leave("dir", "read_directory", istate->repo);
27722839
return dir->nr;
27732840
}
27742841

@@ -2784,23 +2851,15 @@ int read_directory(struct dir_struct *dir, struct index_state *istate,
27842851
QSORT(dir->entries, dir->nr, cmp_dir_entry);
27852852
QSORT(dir->ignored, dir->ignored_nr, cmp_dir_entry);
27862853

2787-
trace_performance_leave("read directory %.*s", len, path);
2854+
emit_traversal_statistics(dir, istate->repo, path, len);
2855+
2856+
trace2_region_leave("dir", "read_directory", istate->repo);
27882857
if (dir->untracked) {
27892858
static int force_untracked_cache = -1;
2790-
static struct trace_key trace_untracked_stats = TRACE_KEY_INIT(UNTRACKED_STATS);
27912859

27922860
if (force_untracked_cache < 0)
27932861
force_untracked_cache =
27942862
git_env_bool("GIT_FORCE_UNTRACKED_CACHE", 0);
2795-
trace_printf_key(&trace_untracked_stats,
2796-
"node creation: %u\n"
2797-
"gitignore invalidation: %u\n"
2798-
"directory invalidation: %u\n"
2799-
"opendir: %u\n",
2800-
dir->untracked->dir_created,
2801-
dir->untracked->gitignore_invalidated,
2802-
dir->untracked->dir_invalidated,
2803-
dir->untracked->dir_opened);
28042863
if (force_untracked_cache &&
28052864
dir->untracked == istate->untracked &&
28062865
(dir->untracked->dir_opened ||
@@ -2811,6 +2870,7 @@ int read_directory(struct dir_struct *dir, struct index_state *istate,
28112870
FREE_AND_NULL(dir->untracked);
28122871
}
28132872
}
2873+
28142874
return dir->nr;
28152875
}
28162876

@@ -2892,11 +2952,9 @@ int is_empty_dir(const char *path)
28922952
if (!dir)
28932953
return 0;
28942954

2895-
while ((e = readdir(dir)) != NULL)
2896-
if (!is_dot_or_dotdot(e->d_name)) {
2897-
ret = 0;
2898-
break;
2899-
}
2955+
e = readdir_skip_dot_and_dotdot(dir);
2956+
if (e)
2957+
ret = 0;
29002958

29012959
closedir(dir);
29022960
return ret;
@@ -2936,10 +2994,8 @@ static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)
29362994
strbuf_complete(path, '/');
29372995

29382996
len = path->len;
2939-
while ((e = readdir(dir)) != NULL) {
2997+
while ((e = readdir_skip_dot_and_dotdot(dir)) != NULL) {
29402998
struct stat st;
2941-
if (is_dot_or_dotdot(e->d_name))
2942-
continue;
29432999

29443000
strbuf_setlen(path, len);
29453001
strbuf_addstr(path, e->d_name);

dir.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,8 +336,14 @@ struct dir_struct {
336336
struct oid_stat ss_info_exclude;
337337
struct oid_stat ss_excludes_file;
338338
unsigned unmanaged_exclude_files;
339+
340+
/* Stats about the traversal */
341+
unsigned visited_paths;
342+
unsigned visited_directories;
339343
};
340344

345+
struct dirent *readdir_skip_dot_and_dotdot(DIR *dirp);
346+
341347
/*Count the number of slashes for string s*/
342348
int count_slashes(const char *s);
343349

entry.c

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,9 @@ static void remove_subtree(struct strbuf *path)
5858

5959
if (!dir)
6060
die_errno("cannot opendir '%s'", path->buf);
61-
while ((de = readdir(dir)) != NULL) {
61+
while ((de = readdir_skip_dot_and_dotdot(dir)) != NULL) {
6262
struct stat st;
6363

64-
if (is_dot_or_dotdot(de->d_name))
65-
continue;
66-
6764
strbuf_addch(path, '/');
6865
strbuf_addstr(path, de->d_name);
6966
if (lstat(path->buf, &st))

notes-merge.c

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -695,13 +695,10 @@ int notes_merge_commit(struct notes_merge_options *o,
695695

696696
strbuf_addch(&path, '/');
697697
baselen = path.len;
698-
while ((e = readdir(dir)) != NULL) {
698+
while ((e = readdir_skip_dot_and_dotdot(dir)) != NULL) {
699699
struct stat st;
700700
struct object_id obj_oid, blob_oid;
701701

702-
if (is_dot_or_dotdot(e->d_name))
703-
continue;
704-
705702
if (get_oid_hex(e->d_name, &obj_oid)) {
706703
if (o->verbosity >= 3)
707704
printf("Skipping non-SHA1 entry '%s%s'\n",

object-file.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2350,10 +2350,8 @@ int for_each_file_in_obj_subdir(unsigned int subdir_nr,
23502350
strbuf_addch(path, '/');
23512351
baselen = path->len;
23522352

2353-
while ((de = readdir(dir))) {
2353+
while ((de = readdir_skip_dot_and_dotdot(dir))) {
23542354
size_t namelen;
2355-
if (is_dot_or_dotdot(de->d_name))
2356-
continue;
23572355

23582356
namelen = strlen(de->d_name);
23592357
strbuf_setlen(path, baselen);

packfile.c

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -813,10 +813,7 @@ void for_each_file_in_pack_dir(const char *objdir,
813813
}
814814
strbuf_addch(&path, '/');
815815
dirnamelen = path.len;
816-
while ((de = readdir(dir)) != NULL) {
817-
if (is_dot_or_dotdot(de->d_name))
818-
continue;
819-
816+
while ((de = readdir_skip_dot_and_dotdot(dir)) != NULL) {
820817
strbuf_setlen(&path, dirnamelen);
821818
strbuf_addstr(&path, de->d_name);
822819

0 commit comments

Comments
 (0)