dir.c: git-status --ignored: don't scan the work tree twice

'git-status --ignored' still scans the work tree twice to collect untracked and ignored files, respectively. fill_directory / read_directory already supports collecting untracked and ignored files in a single directory scan. However, the DIR_COLLECT_IGNORED flag to enable this has some git-add specific side-effects (e.g. it doesn't recurse into ignored directories, so listing ignored files with --untracked=all doesn't work). The DIR_SHOW_IGNORED flag doesn't list untracked files and returns ignored files in dir_struct.entries[] (instead of dir_struct.ignored[] as DIR_COLLECT_IGNORED). DIR_SHOW_IGNORED is used all throughout git. We don't want to break the existing API, so lets introduce a new flag DIR_SHOW_IGNORED_TOO that lists untracked as well as ignored files similar to DIR_COLLECT_FILES, but will recurse into sub-directories based on the other flags as DIR_SHOW_IGNORED does. In dir.c::read_directory_recursive, add ignored files to either dir_struct.entries[] or dir_struct.ignored[] based on the flags. Also move the DIR_COLLECT_IGNORED case here so that filling result lists is in a common place. In wt-status.c::wt_status_collect_untracked, use the new flag and read results from dir_struct.ignored[]. Remove the extra fill_directory call. builtin/check-ignore.c doesn't call fill_directory, setting the git-add specific DIR_COLLECT_IGNORED flag has no effect here. Remove for clarity. Update API documentation to reflect the changes. Performance: with this patch, 'git-status --ignored' is typically as fast as 'git-status'. 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:15 UTC 0aaf62b6e018484bad9cea47dc00644d57b7ad49
5 files changed +41 -22
Documentation/technical/api-directory-listing.txt
+22 -3
@@ -22,12 +22,23 @@ The notable options are:
22
23 `flags`::
24
25 - A bit-field of options:
25 + A bit-field of options (the `*IGNORED*` flags are mutually exclusive):
26
27 `DIR_SHOW_IGNORED`:::
28
29 - The traversal is for finding just ignored files, not unignored
30 - files.
29 + Return just ignored files in `entries[]`, not untracked files.
30 +
31 +`DIR_SHOW_IGNORED_TOO`:::
32 +
33 + Similar to `DIR_SHOW_IGNORED`, but return ignored files in `ignored[]`
34 + in addition to untracked files in `entries[]`.
35 +
36 +`DIR_COLLECT_IGNORED`:::
37 +
38 + Special mode for git-add. Return ignored files in `ignored[]` and
39 + untracked files in `entries[]`. Only returns ignored files that match
40 + pathspec exactly (no wildcards). Does not recurse into ignored
41 + directories.
42
43 `DIR_SHOW_OTHER_DIRECTORIES`:::
44
@@ -57,6 +68,14 @@ The result of the enumeration is left in these fields:
68
69 Internal use; keeps track of allocation of `entries[]` array.
70
71 +`ignored[]`::
72 +
73 + An array of `struct dir_entry`, used for ignored paths with the
74 + `DIR_SHOW_IGNORED_TOO` and `DIR_COLLECT_IGNORED` flags.
75 +
76 +`ignored_nr`::
77 +
78 + The number of members in `ignored[]` array.
79
80 Calling sequence
81 ----------------
builtin/check-ignore.c
-1
@@ -66,7 +66,6 @@ static int check_ignore(const char *prefix, const char **pathspec)
66 die(_("index file corrupt"));
67
68 memset(&dir, 0, sizeof(dir));
69 - dir.flags |= DIR_COLLECT_IGNORED;
69 setup_standard_excludes(&dir);
70
71 if (!pathspec || !*pathspec) {
dir.c
+6 -4
@@ -1183,15 +1183,12 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
1183 return path_none;
1184
1185 exclude = is_excluded(dir, path->buf, &dtype);
1186 - if (exclude && (dir->flags & DIR_COLLECT_IGNORED)
1187 - && exclude_matches_pathspec(path->buf, path->len, simplify))
1188 - dir_add_ignored(dir, path->buf, path->len);
1186
1187 /*
1188 * Excluded? If we don't explicitly want to show
1189 * ignored files, ignore it
1190 */
1194 - if (exclude && !(dir->flags & DIR_SHOW_IGNORED))
1191 + if (exclude && !(dir->flags & (DIR_SHOW_IGNORED|DIR_SHOW_IGNORED_TOO)))
1192 return path_excluded;
1193
1194 switch (dtype) {
@@ -1280,6 +1277,11 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
1277 case path_excluded:
1278 if (dir->flags & DIR_SHOW_IGNORED)
1279 dir_add_name(dir, path.buf, path.len);
1280 + else if ((dir->flags & DIR_SHOW_IGNORED_TOO) ||
1281 + ((dir->flags & DIR_COLLECT_IGNORED) &&
1282 + exclude_matches_pathspec(path.buf, path.len,
1283 + simplify)))
1284 + dir_add_ignored(dir, path.buf, path.len);
1285 break;
1286
1287 case path_untracked:
dir.h
+2 -1
@@ -79,7 +79,8 @@ struct dir_struct {
79 DIR_SHOW_OTHER_DIRECTORIES = 1<<1,
80 DIR_HIDE_EMPTY_DIRECTORIES = 1<<2,
81 DIR_NO_GITLINKS = 1<<3,
82 - DIR_COLLECT_IGNORED = 1<<4
82 + DIR_COLLECT_IGNORED = 1<<4,
83 + DIR_SHOW_IGNORED_TOO = 1<<5
84 } flags;
85 struct dir_entry **entries;
86 struct dir_entry **ignored;
wt-status.c
+11 -13
@@ -511,9 +511,12 @@ static void wt_status_collect_untracked(struct wt_status *s)
511 if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)
512 dir.flags |=
513 DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
514 + if (s->show_ignored_files)
515 + dir.flags |= DIR_SHOW_IGNORED_TOO;
516 setup_standard_excludes(&dir);
517
518 fill_directory(&dir, s->pathspec);
519 +
520 for (i = 0; i < dir.nr; i++) {
521 struct dir_entry *ent = dir.entries[i];
522 if (cache_name_is_other(ent->name, ent->len) &&
@@ -522,22 +525,17 @@ static void wt_status_collect_untracked(struct wt_status *s)
525 free(ent);
526 }
527
525 - if (s->show_ignored_files) {
526 - dir.nr = 0;
527 - dir.flags = DIR_SHOW_IGNORED;
528 - if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)
529 - dir.flags |= DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
530 - fill_directory(&dir, s->pathspec);
531 - for (i = 0; i < dir.nr; i++) {
532 - struct dir_entry *ent = dir.entries[i];
533 - if (cache_name_is_other(ent->name, ent->len) &&
534 - match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
535 - string_list_insert(&s->ignored, ent->name);
536 - free(ent);
537 - }
528 + for (i = 0; i < dir.ignored_nr; i++) {
529 + struct dir_entry *ent = dir.ignored[i];
530 + if (cache_name_is_other(ent->name, ent->len) &&
531 + match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
532 + string_list_insert(&s->ignored, ent->name);
533 + free(ent);
534 }
535
536 free(dir.entries);
537 + free(dir.ignored);
538 + clear_directory(&dir);
539
540 if (advice_status_u_option) {
541 struct timeval t_end;