Merge branch 'jm/status-ignored-files-list'

The set of paths output from "git status --ignored" was tied
closely with its "--untracked=<mode>" option, but now it can be
controlled more flexibly.  Most notably, a directory that is
ignored because it is listed to be ignored in the ignore/exclude
mechanism can be handled differently from a directory that ends up
to be ignored only because all files in it are ignored.

* jm/status-ignored-files-list:
  status: test ignored modes
  status: document options to show matching ignored files
  status: report matching ignored and normal untracked
  status: add option to show ignored files differently
This commit is contained in:
Junio C Hamano 2017-11-13 14:44:59 +09:00
commit d8df70f273
8 changed files with 360 additions and 18 deletions

View file

@ -97,8 +97,27 @@ configuration variable documented in linkgit:git-config[1].
(and suppresses the output of submodule summaries when the config option
`status.submoduleSummary` is set).
--ignored::
--ignored[=<mode>]::
Show ignored files as well.
+
The mode parameter is used to specify the handling of ignored files.
It is optional: it defaults to 'traditional'.
+
The possible options are:
+
- 'traditional' - Shows ignored files and directories, unless
--untracked-files=all is specifed, in which case
individual files in ignored directories are
displayed.
- 'no' - Show no ignored files.
- 'matching' - Shows ignored files and directories matching an
ignore pattern.
+
When 'matching' mode is specified, paths that explicity match an
ignored pattern are shown. If a directory matches an ignore pattern,
then it is shown, but not paths contained in the ignored directory. If
a directory does not match an ignore pattern, but all contents are
ignored, then the directory is not shown, but all contents are shown.
-z::
Terminate entries with NUL, instead of LF. This implies

View file

@ -22,16 +22,20 @@ The notable options are:
`flags`::
A bit-field of options (the `*IGNORED*` flags are mutually exclusive):
A bit-field of options:
`DIR_SHOW_IGNORED`:::
Return just ignored files in `entries[]`, not untracked files.
Return just ignored files in `entries[]`, not untracked
files. This flag is mutually exclusive with
`DIR_SHOW_IGNORED_TOO`.
`DIR_SHOW_IGNORED_TOO`:::
Similar to `DIR_SHOW_IGNORED`, but return ignored files in `ignored[]`
in addition to untracked files in `entries[]`.
Similar to `DIR_SHOW_IGNORED`, but return ignored files in
`ignored[]` in addition to untracked files in
`entries[]`. This flag is mutually exclusive with
`DIR_SHOW_IGNORED`.
`DIR_KEEP_UNTRACKED_CONTENTS`:::
@ -39,6 +43,21 @@ The notable options are:
untracked contents of untracked directories are also returned in
`entries[]`.
`DIR_SHOW_IGNORED_TOO_MODE_MATCHING`:::
Only has meaning if `DIR_SHOW_IGNORED_TOO` is also set; if
this is set, returns ignored files and directories that match
an exclude pattern. If a directory matches an exclude pattern,
then the directory is returned and the contained paths are
not. A directory that does not match an exclude pattern will
not be returned even if all of its contents are ignored. In
this case, the contents are returned as individual entries.
+
If this is set, files and directories that explicity match an ignore
pattern are reported. Implicity ignored directories (directories that
do not match an ignore pattern, but whose contents are all ignored)
are not reported, instead all of the contents are reported.
`DIR_COLLECT_IGNORED`:::
Special mode for git-add. Return ignored files in `ignored[]` and

View file

@ -118,7 +118,7 @@ static int edit_flag = -1; /* unspecified */
static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
static int config_commit_verbose = -1; /* unspecified */
static int no_post_rewrite, allow_empty_message;
static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
static char *sign_commit;
/*
@ -139,7 +139,7 @@ static const char *cleanup_arg;
static enum commit_whence whence;
static int sequencer_in_use;
static int use_editor = 1, include_status = 1;
static int show_ignored_in_status, have_option_m;
static int have_option_m;
static struct strbuf message = STRBUF_INIT;
static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
@ -1076,6 +1076,19 @@ static const char *find_author_by_nickname(const char *name)
die(_("--author '%s' is not 'Name <email>' and matches no existing author"), name);
}
static void handle_ignored_arg(struct wt_status *s)
{
if (!ignored_arg)
; /* default already initialized */
else if (!strcmp(ignored_arg, "traditional"))
s->show_ignored_mode = SHOW_TRADITIONAL_IGNORED;
else if (!strcmp(ignored_arg, "no"))
s->show_ignored_mode = SHOW_NO_IGNORED;
else if (!strcmp(ignored_arg, "matching"))
s->show_ignored_mode = SHOW_MATCHING_IGNORED;
else
die(_("Invalid ignored mode '%s'"), ignored_arg);
}
static void handle_untracked_files_arg(struct wt_status *s)
{
@ -1364,8 +1377,10 @@ int cmd_status(int argc, const char **argv, const char *prefix)
N_("mode"),
N_("show untracked files, optional modes: all, normal, no. (Default: all)"),
PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
OPT_BOOL(0, "ignored", &show_ignored_in_status,
N_("show ignored files")),
{ OPTION_STRING, 0, "ignored", &ignored_arg,
N_("mode"),
N_("show ignored files, optional modes: traditional, matching, no. (Default: traditional)"),
PARSE_OPT_OPTARG, NULL, (intptr_t)"traditional" },
{ OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, N_("when"),
N_("ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)"),
PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
@ -1384,8 +1399,12 @@ int cmd_status(int argc, const char **argv, const char *prefix)
finalize_deferred_config(&s);
handle_untracked_files_arg(&s);
if (show_ignored_in_status)
s.show_ignored_files = 1;
handle_ignored_arg(&s);
if (s.show_ignored_mode == SHOW_MATCHING_IGNORED &&
s.show_untracked_files == SHOW_NO_UNTRACKED_FILES)
die(_("Unsupported combination of ignored and untracked-files arguments"));
parse_pathspec(&s.pathspec, 0,
PATHSPEC_PREFER_FULL,
prefix, argv);

44
dir.c
View file

@ -1389,6 +1389,30 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
case index_nonexistent:
if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
break;
if (exclude &&
(dir->flags & DIR_SHOW_IGNORED_TOO) &&
(dir->flags & DIR_SHOW_IGNORED_TOO_MODE_MATCHING)) {
/*
* This is an excluded directory and we are
* showing ignored paths that match an exclude
* pattern. (e.g. show directory as ignored
* only if it matches an exclude pattern).
* This path will either be 'path_excluded`
* (if we are showing empty directories or if
* the directory is not empty), or will be
* 'path_none' (empty directory, and we are
* not showing empty directories).
*/
if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
return path_excluded;
if (read_directory_recursive(dir, istate, dirname, len,
untracked, 1, 1, pathspec) == path_excluded)
return path_excluded;
return path_none;
}
if (!(dir->flags & DIR_NO_GITLINKS)) {
struct object_id oid;
if (resolve_gitlink_ref(dirname, "HEAD", &oid) == 0)
@ -1561,6 +1585,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
{
int exclude;
int has_path_in_index = !!index_file_exists(istate, path->buf, path->len, ignore_case);
enum path_treatment path_treatment;
if (dtype == DT_UNKNOWN)
dtype = get_dtype(de, istate, path->buf, path->len);
@ -1607,8 +1632,23 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
return path_none;
case DT_DIR:
strbuf_addch(path, '/');
return treat_directory(dir, istate, untracked, path->buf, path->len,
baselen, exclude, pathspec);
path_treatment = treat_directory(dir, istate, untracked,
path->buf, path->len,
baselen, exclude, pathspec);
/*
* If 1) we only want to return directories that
* match an exclude pattern and 2) this directory does
* not match an exclude pattern but all of its
* contents are excluded, then indicate that we should
* recurse into this directory (instead of marking the
* directory itself as an ignored path).
*/
if (!exclude &&
path_treatment == path_excluded &&
(dir->flags & DIR_SHOW_IGNORED_TOO) &&
(dir->flags & DIR_SHOW_IGNORED_TOO_MODE_MATCHING))
return path_recurse;
return path_treatment;
case DT_REG:
case DT_LNK:
return exclude ? path_excluded : path_untracked;

3
dir.h
View file

@ -152,7 +152,8 @@ struct dir_struct {
DIR_COLLECT_IGNORED = 1<<4,
DIR_SHOW_IGNORED_TOO = 1<<5,
DIR_COLLECT_KILLED_ONLY = 1<<6,
DIR_KEEP_UNTRACKED_CONTENTS = 1<<7
DIR_KEEP_UNTRACKED_CONTENTS = 1<<7,
DIR_SHOW_IGNORED_TOO_MODE_MATCHING = 1<<8
} flags;
struct dir_entry **entries;
struct dir_entry **ignored;

233
t/t7521-ignored-mode.sh Executable file
View file

@ -0,0 +1,233 @@
#!/bin/sh
test_description='git status ignored modes'
. ./test-lib.sh
test_expect_success 'setup initial commit and ignore file' '
cat >.gitignore <<-\EOF &&
*.ign
ignored_dir/
!*.unignore
EOF
git add . &&
git commit -m "Initial commit"
'
test_expect_success 'Verify behavior of status on directories with ignored files' '
test_when_finished "git clean -fdx" &&
cat >expect <<-\EOF &&
? expect
? output
! dir/ignored/ignored_1.ign
! dir/ignored/ignored_2.ign
! ignored/ignored_1.ign
! ignored/ignored_2.ign
EOF
mkdir -p ignored dir/ignored &&
touch ignored/ignored_1.ign ignored/ignored_2.ign \
dir/ignored/ignored_1.ign dir/ignored/ignored_2.ign &&
git status --porcelain=v2 --ignored=matching --untracked-files=all >output &&
test_i18ncmp expect output
'
test_expect_success 'Verify status behavior on directory with tracked & ignored files' '
test_when_finished "git clean -fdx && git reset HEAD~1 --hard" &&
cat >expect <<-\EOF &&
? expect
? output
! dir/tracked_ignored/ignored_1.ign
! dir/tracked_ignored/ignored_2.ign
! tracked_ignored/ignored_1.ign
! tracked_ignored/ignored_2.ign
EOF
mkdir -p tracked_ignored dir/tracked_ignored &&
touch tracked_ignored/tracked_1 tracked_ignored/tracked_2 \
tracked_ignored/ignored_1.ign tracked_ignored/ignored_2.ign \
dir/tracked_ignored/tracked_1 dir/tracked_ignored/tracked_2 \
dir/tracked_ignored/ignored_1.ign dir/tracked_ignored/ignored_2.ign &&
git add tracked_ignored/tracked_1 tracked_ignored/tracked_2 \
dir/tracked_ignored/tracked_1 dir/tracked_ignored/tracked_2 &&
git commit -m "commit tracked files" &&
git status --porcelain=v2 --ignored=matching --untracked-files=all >output &&
test_i18ncmp expect output
'
test_expect_success 'Verify status behavior on directory with untracked and ignored files' '
test_when_finished "git clean -fdx" &&
cat >expect <<-\EOF &&
? dir/untracked_ignored/untracked_1
? dir/untracked_ignored/untracked_2
? expect
? output
? untracked_ignored/untracked_1
? untracked_ignored/untracked_2
! dir/untracked_ignored/ignored_1.ign
! dir/untracked_ignored/ignored_2.ign
! untracked_ignored/ignored_1.ign
! untracked_ignored/ignored_2.ign
EOF
mkdir -p untracked_ignored dir/untracked_ignored &&
touch untracked_ignored/untracked_1 untracked_ignored/untracked_2 \
untracked_ignored/ignored_1.ign untracked_ignored/ignored_2.ign \
dir/untracked_ignored/untracked_1 dir/untracked_ignored/untracked_2 \
dir/untracked_ignored/ignored_1.ign dir/untracked_ignored/ignored_2.ign &&
git status --porcelain=v2 --ignored=matching --untracked-files=all >output &&
test_i18ncmp expect output
'
test_expect_success 'Verify status matching ignored files on ignored directory' '
test_when_finished "git clean -fdx" &&
cat >expect <<-\EOF &&
? expect
? output
! ignored_dir/
EOF
mkdir ignored_dir &&
touch ignored_dir/ignored_1 ignored_dir/ignored_2 \
ignored_dir/ignored_1.ign ignored_dir/ignored_2.ign &&
git status --porcelain=v2 --ignored=matching --untracked-files=all >output &&
test_i18ncmp expect output
'
test_expect_success 'Verify status behavior on ignored directory containing tracked file' '
test_when_finished "git clean -fdx && git reset HEAD~1 --hard" &&
cat >expect <<-\EOF &&
? expect
? output
! ignored_dir/ignored_1
! ignored_dir/ignored_1.ign
! ignored_dir/ignored_2
! ignored_dir/ignored_2.ign
EOF
mkdir ignored_dir &&
touch ignored_dir/ignored_1 ignored_dir/ignored_2 \
ignored_dir/ignored_1.ign ignored_dir/ignored_2.ign \
ignored_dir/tracked &&
git add -f ignored_dir/tracked &&
git commit -m "Force add file in ignored directory" &&
git status --porcelain=v2 --ignored=matching --untracked-files=all >output &&
test_i18ncmp expect output
'
test_expect_success 'Verify matching ignored files with --untracked-files=normal' '
test_when_finished "git clean -fdx" &&
cat >expect <<-\EOF &&
? expect
? output
? untracked_dir/
! ignored_dir/
! ignored_files/ignored_1.ign
! ignored_files/ignored_2.ign
EOF
mkdir ignored_dir ignored_files untracked_dir &&
touch ignored_dir/ignored_1 ignored_dir/ignored_2 \
ignored_files/ignored_1.ign ignored_files/ignored_2.ign \
untracked_dir/untracked &&
git status --porcelain=v2 --ignored=matching --untracked-files=normal >output &&
test_i18ncmp expect output
'
test_expect_success 'Verify matching ignored files with --untracked-files=normal' '
test_when_finished "git clean -fdx" &&
cat >expect <<-\EOF &&
? expect
? output
? untracked_dir/
! ignored_dir/
! ignored_files/ignored_1.ign
! ignored_files/ignored_2.ign
EOF
mkdir ignored_dir ignored_files untracked_dir &&
touch ignored_dir/ignored_1 ignored_dir/ignored_2 \
ignored_files/ignored_1.ign ignored_files/ignored_2.ign \
untracked_dir/untracked &&
git status --porcelain=v2 --ignored=matching --untracked-files=normal >output &&
test_i18ncmp expect output
'
test_expect_success 'Verify status behavior on ignored directory containing tracked file' '
test_when_finished "git clean -fdx && git reset HEAD~1 --hard" &&
cat >expect <<-\EOF &&
? expect
? output
! ignored_dir/ignored_1
! ignored_dir/ignored_1.ign
! ignored_dir/ignored_2
! ignored_dir/ignored_2.ign
EOF
mkdir ignored_dir &&
touch ignored_dir/ignored_1 ignored_dir/ignored_2 \
ignored_dir/ignored_1.ign ignored_dir/ignored_2.ign \
ignored_dir/tracked &&
git add -f ignored_dir/tracked &&
git commit -m "Force add file in ignored directory" &&
git status --porcelain=v2 --ignored=matching --untracked-files=normal >output &&
test_i18ncmp expect output
'
test_expect_success 'Verify behavior of status with --ignored=no' '
test_when_finished "git clean -fdx" &&
cat >expect <<-\EOF &&
? expect
? output
EOF
mkdir -p ignored dir/ignored &&
touch ignored/ignored_1.ign ignored/ignored_2.ign \
dir/ignored/ignored_1.ign dir/ignored/ignored_2.ign &&
git status --porcelain=v2 --ignored=no --untracked-files=all >output &&
test_i18ncmp expect output
'
test_expect_success 'Verify behavior of status with --ignored=traditional and --untracked-files=all' '
test_when_finished "git clean -fdx" &&
cat >expect <<-\EOF &&
? expect
? output
! dir/ignored/ignored_1.ign
! dir/ignored/ignored_2.ign
! ignored/ignored_1.ign
! ignored/ignored_2.ign
EOF
mkdir -p ignored dir/ignored &&
touch ignored/ignored_1.ign ignored/ignored_2.ign \
dir/ignored/ignored_1.ign dir/ignored/ignored_2.ign &&
git status --porcelain=v2 --ignored=traditional --untracked-files=all >output &&
test_i18ncmp expect output
'
test_expect_success 'Verify behavior of status with --ignored=traditional and --untracked-files=normal' '
test_when_finished "git clean -fdx" &&
cat >expect <<-\EOF &&
? expect
? output
! dir/
! ignored/
EOF
mkdir -p ignored dir/ignored &&
touch ignored/ignored_1.ign ignored/ignored_2.ign \
dir/ignored/ignored_1.ign dir/ignored/ignored_2.ign &&
git status --porcelain=v2 --ignored=traditional --untracked-files=normal >output &&
test_i18ncmp expect output
'
test_done

View file

@ -658,10 +658,15 @@ static void wt_status_collect_untracked(struct wt_status *s)
if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)
dir.flags |=
DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
if (s->show_ignored_files)
if (s->show_ignored_mode) {
dir.flags |= DIR_SHOW_IGNORED_TOO;
else
if (s->show_ignored_mode == SHOW_MATCHING_IGNORED)
dir.flags |= DIR_SHOW_IGNORED_TOO_MODE_MATCHING;
} else {
dir.untracked = the_index.untracked;
}
setup_standard_excludes(&dir);
fill_directory(&dir, &the_index, &s->pathspec);
@ -1619,7 +1624,7 @@ static void wt_longstatus_print(struct wt_status *s)
}
if (s->show_untracked_files) {
wt_longstatus_print_other(s, &s->untracked, _("Untracked files"), "add");
if (s->show_ignored_files)
if (s->show_ignored_mode)
wt_longstatus_print_other(s, &s->ignored, _("Ignored files"), "add -f");
if (advice_status_u_option && 2000 < s->untracked_in_ms) {
status_printf_ln(s, GIT_COLOR_NORMAL, "%s", "");

View file

@ -27,6 +27,12 @@ enum untracked_status_type {
SHOW_ALL_UNTRACKED_FILES
};
enum show_ignored_type {
SHOW_NO_IGNORED,
SHOW_TRADITIONAL_IGNORED,
SHOW_MATCHING_IGNORED,
};
/* from where does this commit originate */
enum commit_whence {
FROM_COMMIT, /* normal */
@ -70,7 +76,7 @@ struct wt_status {
int display_comment_prefix;
int relative_paths;
int submodule_summary;
int show_ignored_files;
enum show_ignored_type show_ignored_mode;
enum untracked_status_type show_untracked_files;
const char *ignore_submodule_arg;
char color_palette[WT_STATUS_MAXSLOT][COLOR_MAXLEN];