Skip to content

Commit 7ab963e

Browse files
committed
Merge branch 'en/fill-directory-fixes-more'
Corner case bugs in "git clean" that stems from a (necessarily for performance reasons) awkward calling convention in the directory enumeration API has been corrected. * en/fill-directory-fixes-more: dir: point treat_leading_path() warning to the right place dir: restructure in a way to avoid passing around a struct dirent dir: treat_leading_path() and read_directory_recursive(), round 2 clean: demonstrate a bug with pathspecs
2 parents f52ab33 + 0cbb605 commit 7ab963e

File tree

2 files changed

+47
-45
lines changed

2 files changed

+47
-45
lines changed

dir.c

Lines changed: 38 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ struct cached_dir {
4141
int nr_files;
4242
int nr_dirs;
4343

44-
struct dirent *de;
44+
const char *d_name;
45+
int d_type;
4546
const char *file;
4647
struct untracked_cache_dir *ucd;
4748
};
@@ -50,8 +51,8 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
5051
struct index_state *istate, const char *path, int len,
5152
struct untracked_cache_dir *untracked,
5253
int check_only, int stop_at_first_file, const struct pathspec *pathspec);
53-
static int get_dtype(struct dirent *de, struct index_state *istate,
54-
const char *path, int len);
54+
static int resolve_dtype(int dtype, struct index_state *istate,
55+
const char *path, int len);
5556

5657
int count_slashes(const char *s)
5758
{
@@ -1215,8 +1216,7 @@ static struct path_pattern *last_matching_pattern_from_list(const char *pathname
12151216
int prefix = pattern->nowildcardlen;
12161217

12171218
if (pattern->flags & PATTERN_FLAG_MUSTBEDIR) {
1218-
if (*dtype == DT_UNKNOWN)
1219-
*dtype = get_dtype(NULL, istate, pathname, pathlen);
1219+
*dtype = resolve_dtype(*dtype, istate, pathname, pathlen);
12201220
if (*dtype != DT_DIR)
12211221
continue;
12221222
}
@@ -1842,10 +1842,9 @@ static int get_index_dtype(struct index_state *istate,
18421842
return DT_UNKNOWN;
18431843
}
18441844

1845-
static int get_dtype(struct dirent *de, struct index_state *istate,
1846-
const char *path, int len)
1845+
static int resolve_dtype(int dtype, struct index_state *istate,
1846+
const char *path, int len)
18471847
{
1848-
int dtype = de ? DTYPE(de) : DT_UNKNOWN;
18491848
struct stat st;
18501849

18511850
if (dtype != DT_UNKNOWN)
@@ -1870,14 +1869,13 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
18701869
struct strbuf *path,
18711870
int baselen,
18721871
const struct pathspec *pathspec,
1873-
int dtype, struct dirent *de)
1872+
int dtype)
18741873
{
18751874
int exclude;
18761875
int has_path_in_index = !!index_file_exists(istate, path->buf, path->len, ignore_case);
18771876
enum path_treatment path_treatment;
18781877

1879-
if (dtype == DT_UNKNOWN)
1880-
dtype = get_dtype(de, istate, path->buf, path->len);
1878+
dtype = resolve_dtype(dtype, istate, path->buf, path->len);
18811879

18821880
/* Always exclude indexed files */
18831881
if (dtype != DT_DIR && has_path_in_index)
@@ -1985,21 +1983,18 @@ static enum path_treatment treat_path(struct dir_struct *dir,
19851983
int baselen,
19861984
const struct pathspec *pathspec)
19871985
{
1988-
int dtype;
1989-
struct dirent *de = cdir->de;
1990-
1991-
if (!de)
1986+
if (!cdir->d_name)
19921987
return treat_path_fast(dir, untracked, cdir, istate, path,
19931988
baselen, pathspec);
1994-
if (is_dot_or_dotdot(de->d_name) || !fspathcmp(de->d_name, ".git"))
1989+
if (is_dot_or_dotdot(cdir->d_name) || !fspathcmp(cdir->d_name, ".git"))
19951990
return path_none;
19961991
strbuf_setlen(path, baselen);
1997-
strbuf_addstr(path, de->d_name);
1992+
strbuf_addstr(path, cdir->d_name);
19981993
if (simplify_away(path->buf, path->len, pathspec))
19991994
return path_none;
20001995

2001-
dtype = DTYPE(de);
2002-
return treat_one_path(dir, untracked, istate, path, baselen, pathspec, dtype, de);
1996+
return treat_one_path(dir, untracked, istate, path, baselen, pathspec,
1997+
cdir->d_type);
20031998
}
20041999

20052000
static void add_untracked(struct untracked_cache_dir *dir, const char *name)
@@ -2087,10 +2082,17 @@ static int open_cached_dir(struct cached_dir *cdir,
20872082

20882083
static int read_cached_dir(struct cached_dir *cdir)
20892084
{
2085+
struct dirent *de;
2086+
20902087
if (cdir->fdir) {
2091-
cdir->de = readdir(cdir->fdir);
2092-
if (!cdir->de)
2088+
de = readdir(cdir->fdir);
2089+
if (!de) {
2090+
cdir->d_name = NULL;
2091+
cdir->d_type = DT_UNKNOWN;
20932092
return -1;
2093+
}
2094+
cdir->d_name = de->d_name;
2095+
cdir->d_type = DTYPE(de);
20942096
return 0;
20952097
}
20962098
while (cdir->nr_dirs < cdir->untracked->dirs_nr) {
@@ -2216,7 +2218,7 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
22162218
/* recurse into subdir if instructed by treat_path */
22172219
if ((state == path_recurse) ||
22182220
((state == path_untracked) &&
2219-
(get_dtype(cdir.de, istate, path.buf, path.len) == DT_DIR) &&
2221+
(resolve_dtype(cdir.d_type, istate, path.buf, path.len) == DT_DIR) &&
22202222
((dir->flags & DIR_SHOW_IGNORED_TOO) ||
22212223
(pathspec &&
22222224
do_match_pathspec(istate, pathspec, path.buf, path.len,
@@ -2308,16 +2310,16 @@ static int treat_leading_path(struct dir_struct *dir,
23082310
* WARNING WARNING WARNING:
23092311
*
23102312
* Any updates to the traversal logic here may need corresponding
2311-
* updates in treat_leading_path(). See the commit message for the
2312-
* commit adding this warning as well as the commit preceding it
2313-
* for details.
2313+
* updates in read_directory_recursive(). See 777b420347 (dir:
2314+
* synchronize treat_leading_path() and read_directory_recursive(),
2315+
* 2019-12-19) and its parent commit for details.
23142316
*/
23152317

23162318
struct strbuf sb = STRBUF_INIT;
2319+
struct strbuf subdir = STRBUF_INIT;
23172320
int prevlen, baselen;
23182321
const char *cp;
23192322
struct cached_dir cdir;
2320-
struct dirent *de;
23212323
enum path_treatment state = path_none;
23222324

23232325
/*
@@ -2342,22 +2344,8 @@ static int treat_leading_path(struct dir_struct *dir,
23422344
if (!len)
23432345
return 1;
23442346

2345-
/*
2346-
* We need a manufactured dirent with sufficient space to store a
2347-
* leading directory component of path in its d_name. Here, we
2348-
* assume that the dirent's d_name is either declared as
2349-
* char d_name[BIG_ENOUGH]
2350-
* or that it is declared at the end of the struct as
2351-
* char d_name[]
2352-
* For either case, padding with len+1 bytes at the end will ensure
2353-
* sufficient storage space.
2354-
*/
2355-
de = xcalloc(1, st_add3(sizeof(struct dirent), len, 1));
23562347
memset(&cdir, 0, sizeof(cdir));
2357-
cdir.de = de;
2358-
#if defined(DT_UNKNOWN) && !defined(NO_D_TYPE_IN_DIRENT)
2359-
de->d_type = DT_DIR;
2360-
#endif
2348+
cdir.d_type = DT_DIR;
23612349
baselen = 0;
23622350
prevlen = 0;
23632351
while (1) {
@@ -2374,15 +2362,20 @@ static int treat_leading_path(struct dir_struct *dir,
23742362
break;
23752363
strbuf_reset(&sb);
23762364
strbuf_add(&sb, path, prevlen);
2377-
memcpy(de->d_name, path+prevlen, baselen-prevlen);
2378-
de->d_name[baselen-prevlen] = '\0';
2365+
strbuf_reset(&subdir);
2366+
strbuf_add(&subdir, path+prevlen, baselen-prevlen);
2367+
cdir.d_name = subdir.buf;
23792368
state = treat_path(dir, NULL, &cdir, istate, &sb, prevlen,
23802369
pathspec);
23812370
if (state == path_untracked &&
2382-
get_dtype(cdir.de, istate, sb.buf, sb.len) == DT_DIR &&
2371+
resolve_dtype(cdir.d_type, istate, sb.buf, sb.len) == DT_DIR &&
23832372
(dir->flags & DIR_SHOW_IGNORED_TOO ||
23842373
do_match_pathspec(istate, pathspec, sb.buf, sb.len,
23852374
baselen, NULL, DO_MATCH_LEADING_PATHSPEC) == MATCHED_RECURSIVELY_LEADING_PATHSPEC)) {
2375+
if (!match_pathspec(istate, pathspec, sb.buf, sb.len,
2376+
0 /* prefix */, NULL,
2377+
0 /* do NOT special case dirs */))
2378+
state = path_none;
23862379
add_path_to_appropriate_result_list(dir, NULL, &cdir,
23872380
istate,
23882381
&sb, baselen,
@@ -2399,7 +2392,7 @@ static int treat_leading_path(struct dir_struct *dir,
23992392
&sb, baselen, pathspec,
24002393
state);
24012394

2402-
free(de);
2395+
strbuf_release(&subdir);
24032396
strbuf_release(&sb);
24042397
return state == path_recurse;
24052398
}

t/t7300-clean.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,4 +737,13 @@ test_expect_success MINGW 'handle clean & core.longpaths = false nicely' '
737737
test_i18ngrep "too long" .git/err
738738
'
739739

740+
test_expect_success 'clean untracked paths by pathspec' '
741+
git init untracked &&
742+
mkdir untracked/dir &&
743+
echo >untracked/dir/file.txt &&
744+
git -C untracked clean -f dir/file.txt &&
745+
ls untracked/dir >actual &&
746+
test_must_be_empty actual
747+
'
748+
740749
test_done

0 commit comments

Comments
 (0)