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 <blees@dcon.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Karsten Blees committed Apr 15, 2013 at 21:14 UTC defd7c7b3717394ee05b454172bf7b1e747af6ae
1 file changed +72 -74
dir.c
+72 -74
@@ -17,7 +17,21 @@ struct path_simplify {
17 const char *path;
18 };
19
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,
35 int check_only, const struct path_simplify *simplify);
36 static int get_dtype(struct dirent *de, const char *path, int len);
37
@@ -1002,35 +1016,26 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len)
1016 *
1017 * (a) if "show_other_directories" is true, we show it as
1018 * 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.
1021 * (b) if it looks like a git directory, and we don't have
1022 * 'no_gitlinks' set we treat it as a gitlink, and show it
1023 * as a directory.
1024 * (c) otherwise, we recurse into it.
1025 */
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,
1027 const char *dirname, int len, int exclude,
1028 const struct path_simplify *simplify)
1029 {
1030 /* The "len-1" is to strip the final '/' */
1031 switch (directory_exists_in_index(dirname, len-1)) {
1032 case index_directory:
1028 - return recurse_into_directory;
1033 + return path_recurse;
1034
1035 case index_gitdir:
1036 if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
1032 - return ignore_directory;
1033 - return show_directory;
1037 + return path_none;
1038 + return path_untracked;
1039
1040 case index_nonexistent:
1041 if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
@@ -1038,32 +1043,17 @@ static enum directory_treatment treat_directory(struct dir_struct *dir,
1043 if (!(dir->flags & DIR_NO_GITLINKS)) {
1044 unsigned char sha1[20];
1045 if (resolve_gitlink_ref(dirname, "HEAD", sha1) == 0)
1041 - return show_directory;
1046 + return path_untracked;
1047 }
1043 - return recurse_into_directory;
1048 + return path_recurse;
1049 }
1050
1051 /* This is the "show_other_directories" case */
1052
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 -
1053 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);
1057 }
1058
1059 /*
@@ -1178,12 +1168,6 @@ static int get_dtype(struct dirent *de, const char *path, int len)
1168 return dtype;
1169 }
1170
1181 -enum path_treatment {
1182 - path_ignored,
1183 - path_handled,
1184 - path_recurse
1185 -};
1186 -
1171 static enum path_treatment treat_one_path(struct dir_struct *dir,
1172 struct strbuf *path,
1173 const struct path_simplify *simplify,
@@ -1196,7 +1180,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
1180 /* Always exclude indexed files */
1181 if (dtype != DT_DIR &&
1182 cache_name_exists(path->buf, path->len, ignore_case))
1199 - return path_ignored;
1183 + return path_none;
1184
1185 exclude = is_excluded(dir, path->buf, &dtype);
1186 if (exclude && (dir->flags & DIR_COLLECT_IGNORED)
@@ -1208,29 +1192,19 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
1192 * ignored files, ignore it
1193 */
1194 if (exclude && !(dir->flags & DIR_SHOW_IGNORED))
1211 - return path_ignored;
1195 + return path_excluded;
1196
1197 switch (dtype) {
1198 default:
1215 - return path_ignored;
1199 + return path_none;
1200 case DT_DIR:
1201 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);
1204 case DT_REG:
1205 case DT_LNK:
1229 - if (exclude == !(dir->flags & DIR_SHOW_IGNORED))
1230 - return path_ignored;
1231 - break;
1206 + return exclude ? path_excluded : path_untracked;
1207 }
1233 - return path_handled;
1208 }
1209
1210 static enum path_treatment treat_path(struct dir_struct *dir,
@@ -1242,11 +1216,11 @@ static enum path_treatment treat_path(struct dir_struct *dir,
1216 int dtype;
1217
1218 if (is_dot_or_dotdot(de->d_name) || !strcmp(de->d_name, ".git"))
1245 - return path_ignored;
1219 + return path_none;
1220 strbuf_setlen(path, baselen);
1221 strbuf_addstr(path, de->d_name);
1222 if (simplify_away(path->buf, path->len, simplify))
1249 - return path_ignored;
1223 + return path_none;
1224
1225 dtype = DTYPE(de);
1226 return treat_one_path(dir, path, simplify, dtype, de);
@@ -1260,14 +1234,16 @@ static enum path_treatment treat_path(struct dir_struct *dir,
1234 *
1235 * Also, we ignore the name ".git" (even if it is not a directory).
1236 * That likely will not change.
1237 + *
1238 + * Returns the most significant path_treatment value encountered in the scan.
1239 */
1264 -static int read_directory_recursive(struct dir_struct *dir,
1240 +static enum path_treatment read_directory_recursive(struct dir_struct *dir,
1241 const char *base, int baselen,
1242 int check_only,
1243 const struct path_simplify *simplify)
1244 {
1245 DIR *fdir;
1270 - int contents = 0;
1246 + enum path_treatment state, subdir_state, dir_state = path_none;
1247 struct dirent *de;
1248 struct strbuf path = STRBUF_INIT;
1249
@@ -1278,26 +1254,48 @@ static int read_directory_recursive(struct dir_struct *dir,
1254 goto out;
1255
1256 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,
1265 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 */
1275 continue;
1286 - case path_ignored:
1287 - continue;
1288 - case path_handled:
1289 - break;
1276 }
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);
1288 break;
1294 - dir_add_name(dir, path.buf, path.len);
1289 +
1290 + default:
1291 + break;
1292 + }
1293 }
1294 closedir(fdir);
1295 out:
1296 strbuf_release(&path);
1297
1300 - return contents;
1298 + return dir_state;
1299 }
1300
1301 static int cmp_name(const void *p1, const void *p2)
@@ -1368,7 +1366,7 @@ static int treat_leading_path(struct dir_struct *dir,
1366 if (simplify_away(sb.buf, sb.len, simplify))
1367 break;
1368 if (treat_one_path(dir, &sb, simplify,
1371 - DT_DIR, NULL) == path_ignored)
1369 + DT_DIR, NULL) == path_none)
1370 break; /* do not recurse into it */
1371 if (len <= baselen) {
1372 rc = 1;