Skip to content

Commit 08f088c

Browse files
committed
Merge branch 'show-ignored-directory'
This branch introduces an experimental option allowing `git status` to list all untracked files individually, but show ignored directories' names only instead of all ignored files. Signed-off-by: Johannes Schindelin <[email protected]>
2 parents c61d25c + 7a33895 commit 08f088c

File tree

8 files changed

+213
-8
lines changed

8 files changed

+213
-8
lines changed

Documentation/git-status.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ configuration variable documented in linkgit:git-config[1].
100100
--ignored::
101101
Show ignored files as well.
102102

103+
--show-ignored-directory::
104+
(EXPERIMENTAL) Show directories that are ignored, instead of individual files.
105+
Does not recurse into excluded directories when listing all
106+
untracked files.
107+
103108
-z::
104109
Terminate entries with NUL, instead of LF. This implies
105110
the `--porcelain=v1` output format if no other format is given.

Documentation/technical/api-directory-listing.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ The notable options are:
3333
Similar to `DIR_SHOW_IGNORED`, but return ignored files in `ignored[]`
3434
in addition to untracked files in `entries[]`.
3535

36+
`DIR_SHOW_IGNORED_DIRECTORY`:::
37+
38+
(EXPERIMENTAL) If this is set, non-empty directories that match an
39+
ignore pattern are returned. The individual files contained in ignored
40+
directories are not included.
41+
3642
`DIR_KEEP_UNTRACKED_CONTENTS`:::
3743

3844
Only has meaning if `DIR_SHOW_IGNORED_TOO` is also set; if this is set, the

builtin/commit.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1334,6 +1334,7 @@ static int git_status_config(const char *k, const char *v, void *cb)
13341334
int cmd_status(int argc, const char **argv, const char *prefix)
13351335
{
13361336
static int no_lock_index = 0;
1337+
static int show_ignored_directory = 0;
13371338
static struct wt_status s;
13381339
int fd;
13391340
struct object_id oid;
@@ -1365,6 +1366,8 @@ int cmd_status(int argc, const char **argv, const char *prefix)
13651366
OPT_COLUMN(0, "column", &s.colopts, N_("list untracked files in columns")),
13661367
OPT_BOOL(0, "no-lock-index", &no_lock_index,
13671368
N_("do not lock the index")),
1369+
OPT_BOOL(0, "show-ignored-directory", &show_ignored_directory,
1370+
N_("(EXPERIMENTAL) Only show directories that match an ignore pattern name.")),
13681371
OPT_END(),
13691372
};
13701373

@@ -1403,6 +1406,7 @@ int cmd_status(int argc, const char **argv, const char *prefix)
14031406
s.ignore_submodule_arg = ignore_submodule_arg;
14041407
s.status_format = status_format;
14051408
s.verbose = verbose;
1409+
s.show_ignored_directory = show_ignored_directory;
14061410

14071411
wt_status_collect(&s);
14081412

dir.c

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ struct cached_dir {
4949
static enum path_treatment read_directory_recursive(struct dir_struct *dir,
5050
struct index_state *istate, const char *path, int len,
5151
struct untracked_cache_dir *untracked,
52-
int check_only, const struct pathspec *pathspec);
52+
int check_only, int stop_at_first_file, const struct pathspec *pathspec);
5353
static int get_dtype(struct dirent *de, struct index_state *istate,
5454
const char *path, int len);
5555

@@ -1389,6 +1389,21 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
13891389
case index_nonexistent:
13901390
if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
13911391
break;
1392+
if (exclude &&
1393+
(dir->flags & DIR_SHOW_IGNORED_TOO) &&
1394+
(dir->flags & DIR_SHOW_IGNORED_DIRECTORY)) {
1395+
1396+
/*
1397+
* This is an excluded directory, and we are only
1398+
* showing the name of a excluded directory.
1399+
* Check to see if there are any contained files
1400+
* to determine if the directory is empty or not.
1401+
*/
1402+
if (read_directory_recursive(dir, istate, dirname, len,
1403+
untracked, 1, 1, pathspec) == path_excluded)
1404+
return path_excluded;
1405+
return path_none;
1406+
}
13921407
if (!(dir->flags & DIR_NO_GITLINKS)) {
13931408
unsigned char sha1[20];
13941409
if (resolve_gitlink_ref(dirname, "HEAD", sha1) == 0)
@@ -1398,16 +1413,16 @@ static enum path_treatment treat_directory(struct dir_struct *dir,
13981413
}
13991414

14001415
/* This is the "show_other_directories" case */
1401-
14021416
if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
14031417
return exclude ? path_excluded : path_untracked;
14041418

14051419
untracked = lookup_untracked(dir->untracked, untracked,
14061420
dirname + baselen, len - baselen);
14071421
return read_directory_recursive(dir, istate, dirname, len,
1408-
untracked, 1, pathspec);
1422+
untracked, 1, 0, pathspec);
14091423
}
14101424

1425+
14111426
/*
14121427
* This is an inexact early pruning of any recursive directory
14131428
* reading - if the path cannot possibly be in the pathspec,
@@ -1633,7 +1648,7 @@ static enum path_treatment treat_path_fast(struct dir_struct *dir,
16331648
* with check_only set.
16341649
*/
16351650
return read_directory_recursive(dir, istate, path->buf, path->len,
1636-
cdir->ucd, 1, pathspec);
1651+
cdir->ucd, 1, 0, pathspec);
16371652
/*
16381653
* We get path_recurse in the first run when
16391654
* directory_exists_in_index() returns index_nonexistent. We
@@ -1798,7 +1813,7 @@ static void close_cached_dir(struct cached_dir *cdir)
17981813
static enum path_treatment read_directory_recursive(struct dir_struct *dir,
17991814
struct index_state *istate, const char *base, int baselen,
18001815
struct untracked_cache_dir *untracked, int check_only,
1801-
const struct pathspec *pathspec)
1816+
int stop_at_first_file, const struct pathspec *pathspec)
18021817
{
18031818
struct cached_dir cdir;
18041819
enum path_treatment state, subdir_state, dir_state = path_none;
@@ -1832,12 +1847,32 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir,
18321847
subdir_state =
18331848
read_directory_recursive(dir, istate, path.buf,
18341849
path.len, ud,
1835-
check_only, pathspec);
1850+
check_only, stop_at_first_file, pathspec);
18361851
if (subdir_state > dir_state)
18371852
dir_state = subdir_state;
18381853
}
18391854

18401855
if (check_only) {
1856+
if (stop_at_first_file) {
1857+
/*
1858+
* In general, if we are stopping at the first found file,
1859+
* We can only signal that a path of at least "excluded" was
1860+
* found. If the first file we find is "excluded" - there might
1861+
* be other untracked files later on that will not be searched.
1862+
*
1863+
* In current usage of this function, stop_at_first_file will
1864+
* only be set when called from a directory that matches the
1865+
* exclude pattern - there should be no untracked files -
1866+
* all contents should be marked as excluded.
1867+
*/
1868+
if (dir_state == path_excluded)
1869+
break;
1870+
else if (dir_state > path_excluded) {
1871+
dir_state = path_excluded;
1872+
break;
1873+
}
1874+
}
1875+
18411876
/* abort early if maximum state has been reached */
18421877
if (dir_state == path_untracked) {
18431878
if (cdir.fdir)
@@ -2108,7 +2143,7 @@ int read_directory(struct dir_struct *dir, struct index_state *istate,
21082143
*/
21092144
dir->untracked = NULL;
21102145
if (!len || treat_leading_path(dir, istate, path, len, pathspec))
2111-
read_directory_recursive(dir, istate, path, len, untracked, 0, pathspec);
2146+
read_directory_recursive(dir, istate, path, len, untracked, 0, 0, pathspec);
21122147
QSORT(dir->entries, dir->nr, cmp_dir_entry);
21132148
QSORT(dir->ignored, dir->ignored_nr, cmp_dir_entry);
21142149

dir.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ struct dir_struct {
152152
DIR_COLLECT_IGNORED = 1<<4,
153153
DIR_SHOW_IGNORED_TOO = 1<<5,
154154
DIR_COLLECT_KILLED_ONLY = 1<<6,
155-
DIR_KEEP_UNTRACKED_CONTENTS = 1<<7
155+
DIR_KEEP_UNTRACKED_CONTENTS = 1<<7,
156+
DIR_SHOW_IGNORED_DIRECTORY = 1<<8
156157
} flags;
157158
struct dir_entry **entries;
158159
struct dir_entry **ignored;
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
#!/bin/sh
2+
#
3+
#
4+
5+
test_description='git status collapse ignored'
6+
7+
. ./test-lib.sh
8+
9+
10+
cat >.gitignore <<\EOF
11+
*.ign
12+
ignored_dir/
13+
!*.unignore
14+
EOF
15+
16+
# commit initial ignore file
17+
test_expect_success 'setup initial commit and ignore file' '
18+
git add . &&
19+
test_tick &&
20+
git commit -m "Initial commit"
21+
'
22+
23+
cat >expect <<\EOF
24+
? expect
25+
? output
26+
! dir/ignored/ignored_1.ign
27+
! dir/ignored/ignored_2.ign
28+
! ignored/ignored_1.ign
29+
! ignored/ignored_2.ign
30+
EOF
31+
32+
# Test status behavior on folder with ignored files
33+
test_expect_success 'setup folder with ignored files' '
34+
mkdir -p ignored dir/ignored &&
35+
touch ignored/ignored_1.ign ignored/ignored_2.ign \
36+
dir/ignored/ignored_1.ign dir/ignored/ignored_2.ign
37+
'
38+
39+
test_expect_success 'Verify behavior of status on folders with ignored files' '
40+
test_when_finished "git clean -fdx" &&
41+
git status --porcelain=v2 --ignored --untracked-files=all --show-ignored-directory >output &&
42+
test_i18ncmp expect output
43+
'
44+
45+
# Test status bahavior on folder with tracked and ignored files
46+
cat >expect <<\EOF
47+
? expect
48+
? output
49+
! dir/tracked_ignored/ignored_1.ign
50+
! dir/tracked_ignored/ignored_2.ign
51+
! tracked_ignored/ignored_1.ign
52+
! tracked_ignored/ignored_2.ign
53+
EOF
54+
55+
test_expect_success 'setup folder with tracked & ignored files' '
56+
mkdir -p tracked_ignored dir/tracked_ignored &&
57+
touch tracked_ignored/tracked_1 tracked_ignored/tracked_2 \
58+
tracked_ignored/ignored_1.ign tracked_ignored/ignored_2.ign \
59+
dir/tracked_ignored/tracked_1 dir/tracked_ignored/tracked_2 \
60+
dir/tracked_ignored/ignored_1.ign dir/tracked_ignored/ignored_2.ign &&
61+
62+
git add tracked_ignored/tracked_1 tracked_ignored/tracked_2 \
63+
dir/tracked_ignored/tracked_1 dir/tracked_ignored/tracked_2 &&
64+
test_tick &&
65+
git commit -m "commit tracked files"
66+
'
67+
68+
test_expect_success 'Verify status on folder with tracked & ignored files' '
69+
test_when_finished "git clean -fdx && git reset HEAD~1 --hard" &&
70+
git status --porcelain=v2 --ignored --untracked-files=all --show-ignored-directory >output &&
71+
test_i18ncmp expect output
72+
'
73+
74+
75+
# Test status behavior on folder with untracked and ignored files
76+
cat >expect <<\EOF
77+
? dir/untracked_ignored/untracked_1
78+
? dir/untracked_ignored/untracked_2
79+
? expect
80+
? output
81+
? untracked_ignored/untracked_1
82+
? untracked_ignored/untracked_2
83+
! dir/untracked_ignored/ignored_1.ign
84+
! dir/untracked_ignored/ignored_2.ign
85+
! untracked_ignored/ignored_1.ign
86+
! untracked_ignored/ignored_2.ign
87+
EOF
88+
89+
test_expect_success 'setup folder with tracked & ignored files' '
90+
mkdir -p untracked_ignored dir/untracked_ignored &&
91+
touch untracked_ignored/untracked_1 untracked_ignored/untracked_2 \
92+
untracked_ignored/ignored_1.ign untracked_ignored/ignored_2.ign \
93+
dir/untracked_ignored/untracked_1 dir/untracked_ignored/untracked_2 \
94+
dir/untracked_ignored/ignored_1.ign dir/untracked_ignored/ignored_2.ign
95+
'
96+
97+
test_expect_success 'Verify status on folder with tracked & ignored files' '
98+
test_when_finished "git clean -fdx" &&
99+
git status --porcelain=v2 --ignored --untracked-files=all --show-ignored-directory >output &&
100+
test_i18ncmp expect output
101+
'
102+
103+
# Test status behavior on ignored folder
104+
cat >expect <<\EOF
105+
? expect
106+
? output
107+
! ignored_dir/
108+
EOF
109+
110+
test_expect_success 'setup folder with tracked & ignored files' '
111+
mkdir ignored_dir &&
112+
touch ignored_dir/ignored_1 ignored_dir/ignored_2 \
113+
ignored_dir/ignored_1.ign ignored_dir/ignored_2.ign
114+
'
115+
116+
test_expect_success 'Verify status on folder with tracked & ignored files' '
117+
test_when_finished "git clean -fdx" &&
118+
git status --porcelain=v2 --ignored --untracked-files=all --show-ignored-directory >output &&
119+
test_i18ncmp expect output
120+
'
121+
122+
# Test status behavior on ignored folder with tracked file
123+
cat >expect <<\EOF
124+
? expect
125+
? output
126+
! ignored_dir/ignored_1
127+
! ignored_dir/ignored_1.ign
128+
! ignored_dir/ignored_2
129+
! ignored_dir/ignored_2.ign
130+
EOF
131+
132+
test_expect_success 'setup folder with tracked & ignored files' '
133+
mkdir ignored_dir &&
134+
touch ignored_dir/ignored_1 ignored_dir/ignored_2 \
135+
ignored_dir/ignored_1.ign ignored_dir/ignored_2.ign \
136+
ignored_dir/tracked &&
137+
git add -f ignored_dir/tracked &&
138+
test_tick &&
139+
git commit -m "Force add file in ignored directory"
140+
'
141+
142+
test_expect_success 'Verify status on folder with tracked & ignored files' '
143+
test_when_finished "git clean -fdx && git reset HEAD~1 --hard" &&
144+
git status --porcelain=v2 --ignored --untracked-files=all --show-ignored-directory >output &&
145+
test_i18ncmp expect output
146+
'
147+
148+
test_done
149+

wt-status.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,10 @@ static void wt_status_collect_untracked(struct wt_status *s)
664664
dir.flags |= DIR_SHOW_IGNORED_TOO;
665665
else
666666
dir.untracked = the_index.untracked;
667+
668+
if (s->show_ignored_directory)
669+
dir.flags |= DIR_SHOW_IGNORED_DIRECTORY;
670+
667671
setup_standard_excludes(&dir);
668672

669673
fill_directory(&dir, &the_index, &s->pathspec);

wt-status.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ struct wt_status {
7272
int submodule_summary;
7373
int show_ignored_files;
7474
enum untracked_status_type show_untracked_files;
75+
int show_ignored_directory;
7576
const char *ignore_submodule_arg;
7677
char color_palette[WT_STATUS_MAXSLOT][COLOR_MAXLEN];
7778
unsigned colopts;

0 commit comments

Comments
 (0)