status: add option to show ignored files differently

Teach the status command more flexibility in how ignored files are reported. Currently, the reporting of ignored files and untracked files are linked. You cannot control how ignored files are reported independently of how untracked files are reported (i.e. `all` vs `normal`). This makes it impossible to show untracked files with the `all` option, but show ignored files with the `normal` option. This work 1) adds the ability to control the reporting of ignored files independently of untracked files and 2) introduces the concept of status reporting ignored paths that explicitly match an ignored pattern. There are 2 benefits to these changes: 1) if a consumer needs all untracked files but not all ignored files, there is a performance benefit to not scanning all contents of an ignored directory and 2) returning ignored files that explicitly match a path allow a consumer to make more informed decisions about when a status result might be stale. This commit implements --ignored=matching with --untracked-files=all. The following commit will implement --ignored=matching with --untracked=files=normal. As an example of where this flexibility could be useful is that our application (Visual Studio) runs the status command and presents the output. It shows all untracked files individually (e.g. using the '--untracked-files==all' option), and would like to know about which paths are ignored. It uses information about ignored paths to make decisions about when the status result might have changed. Additionally, many projects place build output into directories inside a repository's working directory (e.g. in "bin/" and "obj/" directories). Normal usage is to explicitly ignore these 2 directory names in the .gitignore file (rather than or in addition to the *.obj pattern).If an application could know that these directories are explicitly ignored, it could infer that all contents are ignored as well and make better informed decisions about files in these directories. It could infer that any changes under these paths would not affect the output of status. Additionally, there can be a significant performance benefit by avoiding scanning through ignored directories. When status is set to report matching ignored files, it has the following behavior. Ignored files and directories that explicitly match an exclude pattern are reported. If an ignored directory matches an exclude pattern, then the path of the directory is returned. If a directory does not match an exclude pattern, but all of its contents are ignored, then the contained files are reported instead of the directory. Signed-off-by: Jameson Miller <jamill@microsoft.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jameson Miller committed Oct 30, 2017 at 13:21 UTC eec0f7f2b7532f4ec74461f969701911b8876162
5 files changed +66 -11
builtin/commit.c
+25 -6
@@ -118,7 +118,7 @@ static int edit_flag = -1; /* unspecified */
118 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
119 static int config_commit_verbose = -1; /* unspecified */
120 static int no_post_rewrite, allow_empty_message;
121 -static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
121 +static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
122 static char *sign_commit;
123
124 /*
@@ -139,7 +139,7 @@ static const char *cleanup_arg;
139 static enum commit_whence whence;
140 static int sequencer_in_use;
141 static int use_editor = 1, include_status = 1;
142 -static int show_ignored_in_status, have_option_m;
142 +static int have_option_m;
143 static struct strbuf message = STRBUF_INIT;
144
145 static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
@@ -1075,6 +1075,19 @@ static const char *find_author_by_nickname(const char *name)
1075 die(_("--author '%s' is not 'Name <email>' and matches no existing author"), name);
1076 }
1077
1078 +static void handle_ignored_arg(struct wt_status *s)
1079 +{
1080 + if (!ignored_arg)
1081 + ; /* default already initialized */
1082 + else if (!strcmp(ignored_arg, "traditional"))
1083 + s->show_ignored_mode = SHOW_TRADITIONAL_IGNORED;
1084 + else if (!strcmp(ignored_arg, "no"))
1085 + s->show_ignored_mode = SHOW_NO_IGNORED;
1086 + else if (!strcmp(ignored_arg, "matching"))
1087 + s->show_ignored_mode = SHOW_MATCHING_IGNORED;
1088 + else
1089 + die(_("Invalid ignored mode '%s'"), ignored_arg);
1090 +}
1091
1092 static void handle_untracked_files_arg(struct wt_status *s)
1093 {
@@ -1363,8 +1376,10 @@ int cmd_status(int argc, const char **argv, const char *prefix)
1376 N_("mode"),
1377 N_("show untracked files, optional modes: all, normal, no. (Default: all)"),
1378 PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
1366 - OPT_BOOL(0, "ignored", &show_ignored_in_status,
1367 - N_("show ignored files")),
1379 + { OPTION_STRING, 0, "ignored", &ignored_arg,
1380 + N_("mode"),
1381 + N_("show ignored files, optional modes: traditional, matching, no. (Default: traditional)"),
1382 + PARSE_OPT_OPTARG, NULL, (intptr_t)"traditional" },
1383 { OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, N_("when"),
1384 N_("ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)"),
1385 PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
@@ -1383,8 +1398,12 @@ int cmd_status(int argc, const char **argv, const char *prefix)
1398 finalize_deferred_config(&s);
1399
1400 handle_untracked_files_arg(&s);
1386 - if (show_ignored_in_status)
1387 - s.show_ignored_files = 1;
1401 + handle_ignored_arg(&s);
1402 +
1403 + if (s.show_ignored_mode == SHOW_MATCHING_IGNORED &&
1404 + s.show_untracked_files == SHOW_NO_UNTRACKED_FILES)
1405 + die(_("Unsupported combination of ignored and untracked-files arguments"));
1406 +
1407 parse_pathspec(&s.pathspec, 0,
1408 PATHSPEC_PREFER_FULL,
1409 prefix, argv);
dir.c
+24
@@ -1389,6 +1389,30 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
1389 case index_nonexistent:
1390 if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
1391 break;
1392 + if (exclude &&
1393 + (dir->flags & DIR_SHOW_IGNORED_TOO) &&
1394 + (dir->flags & DIR_SHOW_IGNORED_TOO_MODE_MATCHING)) {
1395 +
1396 + /*
1397 + * This is an excluded directory and we are
1398 + * showing ignored paths that match an exclude
1399 + * pattern. (e.g. show directory as ignored
1400 + * only if it matches an exclude pattern).
1401 + * This path will either be 'path_excluded`
1402 + * (if we are showing empty directories or if
1403 + * the directory is not empty), or will be
1404 + * 'path_none' (empty directory, and we are
1405 + * not showing empty directories).
1406 + */
1407 + if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
1408 + return path_excluded;
1409 +
1410 + if (read_directory_recursive(dir, istate, dirname, len,
1411 + untracked, 1, 1, pathspec) == path_excluded)
1412 + return path_excluded;
1413 +
1414 + return path_none;
1415 + }
1416 if (!(dir->flags & DIR_NO_GITLINKS)) {
1417 unsigned char sha1[20];
1418 if (resolve_gitlink_ref(dirname, "HEAD", sha1) == 0)
dir.h
+2 -1
@@ -152,7 +152,8 @@ struct dir_struct {
152 DIR_COLLECT_IGNORED = 1<<4,
153 DIR_SHOW_IGNORED_TOO = 1<<5,
154 DIR_COLLECT_KILLED_ONLY = 1<<6,
155 - DIR_KEEP_UNTRACKED_CONTENTS = 1<<7
155 + DIR_KEEP_UNTRACKED_CONTENTS = 1<<7,
156 + DIR_SHOW_IGNORED_TOO_MODE_MATCHING = 1<<8
157 } flags;
158 struct dir_entry **entries;
159 struct dir_entry **ignored;
wt-status.c
+8 -3
@@ -658,10 +658,15 @@ static void wt_status_collect_untracked(struct wt_status *s)
658 if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)
659 dir.flags |=
660 DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
661 - if (s->show_ignored_files)
661 + if (s->show_ignored_mode) {
662 dir.flags |= DIR_SHOW_IGNORED_TOO;
663 - else
663 +
664 + if (s->show_ignored_mode == SHOW_MATCHING_IGNORED)
665 + dir.flags |= DIR_SHOW_IGNORED_TOO_MODE_MATCHING;
666 + } else {
667 dir.untracked = the_index.untracked;
668 + }
669 +
670 setup_standard_excludes(&dir);
671
672 fill_directory(&dir, &the_index, &s->pathspec);
@@ -1619,7 +1624,7 @@ static void wt_longstatus_print(struct wt_status *s)
1624 }
1625 if (s->show_untracked_files) {
1626 wt_longstatus_print_other(s, &s->untracked, _("Untracked files"), "add");
1622 - if (s->show_ignored_files)
1627 + if (s->show_ignored_mode)
1628 wt_longstatus_print_other(s, &s->ignored, _("Ignored files"), "add -f");
1629 if (advice_status_u_option && 2000 < s->untracked_in_ms) {
1630 status_printf_ln(s, GIT_COLOR_NORMAL, "%s", "");
wt-status.h
+7 -1
@@ -27,6 +27,12 @@ enum untracked_status_type {
27 SHOW_ALL_UNTRACKED_FILES
28 };
29
30 +enum show_ignored_type {
31 + SHOW_NO_IGNORED,
32 + SHOW_TRADITIONAL_IGNORED,
33 + SHOW_MATCHING_IGNORED,
34 +};
35 +
36 /* from where does this commit originate */
37 enum commit_whence {
38 FROM_COMMIT, /* normal */
@@ -70,7 +76,7 @@ struct wt_status {
76 int display_comment_prefix;
77 int relative_paths;
78 int submodule_summary;
73 - int show_ignored_files;
79 + enum show_ignored_type show_ignored_mode;
80 enum untracked_status_type show_untracked_files;
81 const char *ignore_submodule_arg;
82 char color_palette[WT_STATUS_MAXSLOT][COLOR_MAXLEN];