Skip to content

Commit defd7c7

Browse files
kbleesgitster
authored andcommitted
dir.c: git-status --ignored: don't scan the work tree three times
'git-status --ignored' recursively scans directories up to three times: 1. To collect untracked files. 2. To collect ignored files. 3. When collecting ignored files, to check that an untracked directory that potentially contains ignored files doesn't also contain untracked files (i.e. isn't already listed as untracked). Let's get rid of case 3 first. Currently, read_directory_recursive returns a boolean whether a directory contains the requested files or not (actually, it returns the number of files, but no caller actually needs that), and DIR_SHOW_IGNORED specifies what we're looking for. To be able to test for both untracked and ignored files in a single scan, we need to return a bit more info, and the result must be independent of the DIR_SHOW_IGNORED flag. Reuse the path_treatment enum as return value of read_directory_recursive. Split path_handled in two separate values path_excluded and path_untracked that don't change their meaning with the DIR_SHOW_IGNORED flag. We don't need an extra value path_untracked_and_excluded, as directories with both untracked and ignored files should be listed as untracked. Rename path_ignored to path_none for clarity (i.e. "don't treat that path" in contrast to "the path is ignored and should be treated according to DIR_SHOW_IGNORED"). Replace enum directory_treatment with path_treatment. That's just another enum with the same meaning, no need to translate back and forth. In treat_directory, get rid of the extra read_directory_recursive call and all the DIR_SHOW_IGNORED-specific code. In read_directory_recursive, decide whether to dir_add_name path_excluded or path_untracked paths based on the DIR_SHOW_IGNORED flag. The return value of read_directory_recursive is the maximum path_treatment of all files and sub-directories. In the check_only case, abort when we've reached the most significant value (path_untracked). Signed-off-by: Karsten Blees <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 8aaf8d7 commit defd7c7

File tree

1 file changed

+72
-74
lines changed

1 file changed

+72
-74
lines changed

dir.c

Lines changed: 72 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,21 @@ struct path_simplify {
1717
const char *path;
1818
};
1919

20-
static int read_directory_recursive(struct dir_struct *dir, const char *path, int len,
20+
/*
21+
* Tells read_directory_recursive how a file or directory should be treated.
22+
* Values are ordered by significance, e.g. if a directory contains both
23+
* excluded and untracked files, it is listed as untracked because
24+
* path_untracked > path_excluded.
25+
*/
26+
enum path_treatment {
27+
path_none = 0,
28+
path_recurse,
29+
path_excluded,
30+
path_untracked
31+
};
32+
33+
static enum path_treatment read_directory_recursive(struct dir_struct *dir,
34+
const char *path, int len,
2135
int check_only, const struct path_simplify *simplify);
2236
static int get_dtype(struct dirent *de, const char *path, int len);
2337

@@ -1002,68 +1016,44 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len)
10021016
*
10031017
* (a) if "show_other_directories" is true, we show it as
10041018
* just a directory, unless "hide_empty_directories" is
1005-
* also true and the directory is empty, in which case
1006-
* we just ignore it entirely.
1007-
* if we are looking for ignored directories, look if it
1008-
* contains only ignored files to decide if it must be shown as
1009-
* ignored or not.
1019+
* also true, in which case we need to check if it contains any
1020+
* untracked and / or ignored files.
10101021
* (b) if it looks like a git directory, and we don't have
10111022
* 'no_gitlinks' set we treat it as a gitlink, and show it
10121023
* as a directory.
10131024
* (c) otherwise, we recurse into it.
10141025
*/
1015-
enum directory_treatment {
1016-
show_directory,
1017-
ignore_directory,
1018-
recurse_into_directory
1019-
};
1020-
1021-
static enum directory_treatment treat_directory(struct dir_struct *dir,
1026+
static enum path_treatment treat_directory(struct dir_struct *dir,
10221027
const char *dirname, int len, int exclude,
10231028
const struct path_simplify *simplify)
10241029
{
10251030
/* The "len-1" is to strip the final '/' */
10261031
switch (directory_exists_in_index(dirname, len-1)) {
10271032
case index_directory:
1028-
return recurse_into_directory;
1033+
return path_recurse;
10291034

10301035
case index_gitdir:
10311036
if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
1032-
return ignore_directory;
1033-
return show_directory;
1037+
return path_none;
1038+
return path_untracked;
10341039

10351040
case index_nonexistent:
10361041
if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
10371042
break;
10381043
if (!(dir->flags & DIR_NO_GITLINKS)) {
10391044
unsigned char sha1[20];
10401045
if (resolve_gitlink_ref(dirname, "HEAD", sha1) == 0)
1041-
return show_directory;
1046+
return path_untracked;
10421047
}
1043-
return recurse_into_directory;
1048+
return path_recurse;
10441049
}
10451050

10461051
/* This is the "show_other_directories" case */
10471052

1048-
/*
1049-
* We are looking for ignored files and our directory is not ignored,
1050-
* check if it contains untracked files (i.e. is listed as untracked)
1051-
*/
1052-
if ((dir->flags & DIR_SHOW_IGNORED) && !exclude) {
1053-
int ignored;
1054-
dir->flags &= ~DIR_SHOW_IGNORED;
1055-
ignored = read_directory_recursive(dir, dirname, len, 1, simplify);
1056-
dir->flags |= DIR_SHOW_IGNORED;
1057-
1058-
if (ignored)
1059-
return ignore_directory;
1060-
}
1061-
10621053
if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
1063-
return show_directory;
1064-
if (!read_directory_recursive(dir, dirname, len, 1, simplify))
1065-
return ignore_directory;
1066-
return show_directory;
1054+
return exclude ? path_excluded : path_untracked;
1055+
1056+
return read_directory_recursive(dir, dirname, len, 1, simplify);
10671057
}
10681058

10691059
/*
@@ -1178,12 +1168,6 @@ static int get_dtype(struct dirent *de, const char *path, int len)
11781168
return dtype;
11791169
}
11801170

1181-
enum path_treatment {
1182-
path_ignored,
1183-
path_handled,
1184-
path_recurse
1185-
};
1186-
11871171
static enum path_treatment treat_one_path(struct dir_struct *dir,
11881172
struct strbuf *path,
11891173
const struct path_simplify *simplify,
@@ -1196,7 +1180,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
11961180
/* Always exclude indexed files */
11971181
if (dtype != DT_DIR &&
11981182
cache_name_exists(path->buf, path->len, ignore_case))
1199-
return path_ignored;
1183+
return path_none;
12001184

12011185
exclude = is_excluded(dir, path->buf, &dtype);
12021186
if (exclude && (dir->flags & DIR_COLLECT_IGNORED)
@@ -1208,29 +1192,19 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
12081192
* ignored files, ignore it
12091193
*/
12101194
if (exclude && !(dir->flags & DIR_SHOW_IGNORED))
1211-
return path_ignored;
1195+
return path_excluded;
12121196

12131197
switch (dtype) {
12141198
default:
1215-
return path_ignored;
1199+
return path_none;
12161200
case DT_DIR:
12171201
strbuf_addch(path, '/');
1218-
switch (treat_directory(dir, path->buf, path->len, exclude, simplify)) {
1219-
case show_directory:
1220-
break;
1221-
case recurse_into_directory:
1222-
return path_recurse;
1223-
case ignore_directory:
1224-
return path_ignored;
1225-
}
1226-
break;
1202+
return treat_directory(dir, path->buf, path->len, exclude,
1203+
simplify);
12271204
case DT_REG:
12281205
case DT_LNK:
1229-
if (exclude == !(dir->flags & DIR_SHOW_IGNORED))
1230-
return path_ignored;
1231-
break;
1206+
return exclude ? path_excluded : path_untracked;
12321207
}
1233-
return path_handled;
12341208
}
12351209

12361210
static enum path_treatment treat_path(struct dir_struct *dir,
@@ -1242,11 +1216,11 @@ static enum path_treatment treat_path(struct dir_struct *dir,
12421216
int dtype;
12431217

12441218
if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git"))
1245-
return path_ignored;
1219+
return path_none;
12461220
strbuf_setlen(path, baselen);
12471221
strbuf_addstr(path, de->d_name);
12481222
if (simplify_away(path->buf, path->len, simplify))
1249-
return path_ignored;
1223+
return path_none;
12501224

12511225
dtype = DTYPE(de);
12521226
return treat_one_path(dir, path, simplify, dtype, de);
@@ -1260,14 +1234,16 @@ static enum path_treatment treat_path(struct dir_struct *dir,
12601234
*
12611235
* Also, we ignore the name ".git" (even if it is not a directory).
12621236
* That likely will not change.
1237+
*
1238+
* Returns the most significant path_treatment value encountered in the scan.
12631239
*/
1264-
static int read_directory_recursive(struct dir_struct *dir,
1240+
static enum path_treatment read_directory_recursive(struct dir_struct *dir,
12651241
const char *base, int baselen,
12661242
int check_only,
12671243
const struct path_simplify *simplify)
12681244
{
12691245
DIR *fdir;
1270-
int contents = 0;
1246+
enum path_treatment state, subdir_state, dir_state = path_none;
12711247
struct dirent *de;
12721248
struct strbuf path = STRBUF_INIT;
12731249

@@ -1278,26 +1254,48 @@ static int read_directory_recursive(struct dir_struct *dir,
12781254
goto out;
12791255

12801256
while ((de = readdir(fdir)) != NULL) {
1281-
switch (treat_path(dir, de, &path, baselen, simplify)) {
1282-
case path_recurse:
1283-
contents += read_directory_recursive(dir, path.buf,
1257+
/* check how the file or directory should be treated */
1258+
state = treat_path(dir, de, &path, baselen, simplify);
1259+
if (state > dir_state)
1260+
dir_state = state;
1261+
1262+
/* recurse into subdir if instructed by treat_path */
1263+
if (state == path_recurse) {
1264+
subdir_state = read_directory_recursive(dir, path.buf,
12841265
path.len, check_only, simplify);
1266+
if (subdir_state > dir_state)
1267+
dir_state = subdir_state;
1268+
}
1269+
1270+
if (check_only) {
1271+
/* abort early if maximum state has been reached */
1272+
if (dir_state == path_untracked)
1273+
break;
1274+
/* skip the dir_add_* part */
12851275
continue;
1286-
case path_ignored:
1287-
continue;
1288-
case path_handled:
1289-
break;
12901276
}
1291-
contents++;
1292-
if (check_only)
1277+
1278+
/* add the path to the appropriate result list */
1279+
switch (state) {
1280+
case path_excluded:
1281+
if (dir->flags & DIR_SHOW_IGNORED)
1282+
dir_add_name(dir, path.buf, path.len);
1283+
break;
1284+
1285+
case path_untracked:
1286+
if (!(dir->flags & DIR_SHOW_IGNORED))
1287+
dir_add_name(dir, path.buf, path.len);
12931288
break;
1294-
dir_add_name(dir, path.buf, path.len);
1289+
1290+
default:
1291+
break;
1292+
}
12951293
}
12961294
closedir(fdir);
12971295
out:
12981296
strbuf_release(&path);
12991297

1300-
return contents;
1298+
return dir_state;
13011299
}
13021300

13031301
static int cmp_name(const void *p1, const void *p2)
@@ -1368,7 +1366,7 @@ static int treat_leading_path(struct dir_struct *dir,
13681366
if (simplify_away(sb.buf, sb.len, simplify))
13691367
break;
13701368
if (treat_one_path(dir, &sb, simplify,
1371-
DT_DIR, NULL) == path_ignored)
1369+
DT_DIR, NULL) == path_none)
13721370
break; /* do not recurse into it */
13731371
if (len <= baselen) {
13741372
rc = 1;

0 commit comments

Comments
 (0)