Skip to content

Commit 987e315

Browse files
committed
Merge branch 'jc/gitignore-ends-with-slash'
* jc/gitignore-ends-with-slash: gitignore: lazily find dtype gitignore(5): Allow "foo/" in ignore list to match directory "foo"
2 parents 1ae419c + 6831a88 commit 987e315

File tree

7 files changed

+106
-15
lines changed

7 files changed

+106
-15
lines changed

Documentation/gitignore.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@ Patterns have the following format:
5757
included again. If a negated pattern matches, this will
5858
override lower precedence patterns sources.
5959

60+
- If the pattern ends with a slash, it is removed for the
61+
purpose of the following description, but it would only find
62+
a match with a directory. In other words, `foo/` will match a
63+
directory `foo` and paths underneath it, but will not match a
64+
regular file or a symbolic link `foo` (this is consistent
65+
with the way how pathspec works in general in git).
66+
6067
- If the pattern does not contain a slash '/', git treats it as
6168
a shell glob pattern and checks for a match against the
6269
pathname without leading directories.

builtin-ls-files.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,8 @@ static void show_files(struct dir_struct *dir, const char *prefix)
238238
if (show_cached | show_stage) {
239239
for (i = 0; i < active_nr; i++) {
240240
struct cache_entry *ce = active_cache[i];
241-
if (excluded(dir, ce->name) != dir->show_ignored)
241+
int dtype = ce_to_dtype(ce);
242+
if (excluded(dir, ce->name, &dtype) != dir->show_ignored)
242243
continue;
243244
if (show_unmerged && !ce_stage(ce))
244245
continue;
@@ -252,7 +253,8 @@ static void show_files(struct dir_struct *dir, const char *prefix)
252253
struct cache_entry *ce = active_cache[i];
253254
struct stat st;
254255
int err;
255-
if (excluded(dir, ce->name) != dir->show_ignored)
256+
int dtype = ce_to_dtype(ce);
257+
if (excluded(dir, ce->name, &dtype) != dir->show_ignored)
256258
continue;
257259
err = lstat(ce->name, &st);
258260
if (show_deleted && err)

cache.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,18 @@ static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned in
178178
}
179179
return create_ce_mode(mode);
180180
}
181+
static inline int ce_to_dtype(const struct cache_entry *ce)
182+
{
183+
unsigned ce_mode = ntohl(ce->ce_mode);
184+
if (S_ISREG(ce_mode))
185+
return DT_REG;
186+
else if (S_ISDIR(ce_mode) || S_ISGITLINK(ce_mode))
187+
return DT_DIR;
188+
else if (S_ISLNK(ce_mode))
189+
return DT_LNK;
190+
else
191+
return DT_UNKNOWN;
192+
}
181193
#define canon_mode(mode) \
182194
(S_ISREG(mode) ? (S_IFREG | ce_permissions(mode)) : \
183195
S_ISLNK(mode) ? S_IFLNK : S_ISDIR(mode) ? S_IFDIR : S_IFGITLINK)

dir.c

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ struct path_simplify {
1717
static int read_directory_recursive(struct dir_struct *dir,
1818
const char *path, const char *base, int baselen,
1919
int check_only, const struct path_simplify *simplify);
20+
static int get_dtype(struct dirent *de, const char *path);
2021

2122
int common_prefix(const char **pathspec)
2223
{
@@ -126,18 +127,34 @@ static int no_wildcard(const char *string)
126127
void add_exclude(const char *string, const char *base,
127128
int baselen, struct exclude_list *which)
128129
{
129-
struct exclude *x = xmalloc(sizeof (*x));
130+
struct exclude *x;
131+
size_t len;
132+
int to_exclude = 1;
133+
int flags = 0;
130134

131-
x->to_exclude = 1;
132135
if (*string == '!') {
133-
x->to_exclude = 0;
136+
to_exclude = 0;
134137
string++;
135138
}
136-
x->pattern = string;
139+
len = strlen(string);
140+
if (len && string[len - 1] == '/') {
141+
char *s;
142+
x = xmalloc(sizeof(*x) + len);
143+
s = (char*)(x+1);
144+
memcpy(s, string, len - 1);
145+
s[len - 1] = '\0';
146+
string = s;
147+
x->pattern = s;
148+
flags = EXC_FLAG_MUSTBEDIR;
149+
} else {
150+
x = xmalloc(sizeof(*x));
151+
x->pattern = string;
152+
}
153+
x->to_exclude = to_exclude;
137154
x->patternlen = strlen(string);
138155
x->base = base;
139156
x->baselen = baselen;
140-
x->flags = 0;
157+
x->flags = flags;
141158
if (!strchr(string, '/'))
142159
x->flags |= EXC_FLAG_NODIR;
143160
if (no_wildcard(string))
@@ -261,7 +278,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
261278
* Return 1 for exclude, 0 for include and -1 for undecided.
262279
*/
263280
static int excluded_1(const char *pathname,
264-
int pathlen, const char *basename,
281+
int pathlen, const char *basename, int *dtype,
265282
struct exclude_list *el)
266283
{
267284
int i;
@@ -272,6 +289,13 @@ static int excluded_1(const char *pathname,
272289
const char *exclude = x->pattern;
273290
int to_exclude = x->to_exclude;
274291

292+
if (x->flags & EXC_FLAG_MUSTBEDIR) {
293+
if (*dtype == DT_UNKNOWN)
294+
*dtype = get_dtype(NULL, pathname);
295+
if (*dtype != DT_DIR)
296+
continue;
297+
}
298+
275299
if (x->flags & EXC_FLAG_NODIR) {
276300
/* match basename */
277301
if (x->flags & EXC_FLAG_NOWILDCARD) {
@@ -314,7 +338,7 @@ static int excluded_1(const char *pathname,
314338
return -1; /* undecided */
315339
}
316340

317-
int excluded(struct dir_struct *dir, const char *pathname)
341+
int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
318342
{
319343
int pathlen = strlen(pathname);
320344
int st;
@@ -323,7 +347,8 @@ int excluded(struct dir_struct *dir, const char *pathname)
323347

324348
prep_exclude(dir, pathname, basename-pathname);
325349
for (st = EXC_CMDL; st <= EXC_FILE; st++) {
326-
switch (excluded_1(pathname, pathlen, basename, &dir->exclude_list[st])) {
350+
switch (excluded_1(pathname, pathlen, basename,
351+
dtype_p, &dir->exclude_list[st])) {
327352
case 0:
328353
return 0;
329354
case 1:
@@ -508,7 +533,7 @@ static int in_pathspec(const char *path, int len, const struct path_simplify *si
508533

509534
static int get_dtype(struct dirent *de, const char *path)
510535
{
511-
int dtype = DTYPE(de);
536+
int dtype = de ? DTYPE(de) : DT_UNKNOWN;
512537
struct stat st;
513538

514539
if (dtype != DT_UNKNOWN)
@@ -560,7 +585,8 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
560585
if (simplify_away(fullname, baselen + len, simplify))
561586
continue;
562587

563-
exclude = excluded(dir, fullname);
588+
dtype = DTYPE(de);
589+
exclude = excluded(dir, fullname, &dtype);
564590
if (exclude && dir->collect_ignored
565591
&& in_pathspec(fullname, baselen + len, simplify))
566592
dir_add_ignored(dir, fullname, baselen + len);
@@ -572,7 +598,8 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
572598
if (exclude && !dir->show_ignored)
573599
continue;
574600

575-
dtype = get_dtype(de, fullname);
601+
if (dtype == DT_UNKNOWN)
602+
dtype = get_dtype(de, fullname);
576603

577604
/*
578605
* Do we want to see just the ignored files?

dir.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ struct dir_entry {
99
#define EXC_FLAG_NODIR 1
1010
#define EXC_FLAG_NOWILDCARD 2
1111
#define EXC_FLAG_ENDSWITH 4
12+
#define EXC_FLAG_MUSTBEDIR 8
1213

1314
struct exclude_list {
1415
int nr;
@@ -67,7 +68,7 @@ extern int match_pathspec(const char **pathspec, const char *name, int namelen,
6768

6869
extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen, const char **pathspec);
6970

70-
extern int excluded(struct dir_struct *, const char *);
71+
extern int excluded(struct dir_struct *, const char *, int *);
7172
extern void add_excludes_from_file(struct dir_struct *, const char *fname);
7273
extern void add_exclude(const char *string, const char *base,
7374
int baselen, struct exclude_list *which);

t/t3001-ls-files-others-exclude.sh

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,45 @@ EOF
9999
test_expect_success 'git-status honours core.excludesfile' \
100100
'diff -u expect output'
101101

102+
test_expect_success 'trailing slash in exclude allows directory match(1)' '
103+
104+
git ls-files --others --exclude=one/ >output &&
105+
if grep "^one/" output
106+
then
107+
echo Ooops
108+
false
109+
else
110+
: happy
111+
fi
112+
113+
'
114+
115+
test_expect_success 'trailing slash in exclude allows directory match (2)' '
116+
117+
git ls-files --others --exclude=one/two/ >output &&
118+
if grep "^one/two/" output
119+
then
120+
echo Ooops
121+
false
122+
else
123+
: happy
124+
fi
125+
126+
'
127+
128+
test_expect_success 'trailing slash in exclude forces directory match (1)' '
129+
130+
>two
131+
git ls-files --others --exclude=two/ >output &&
132+
grep "^two" output
133+
134+
'
135+
136+
test_expect_success 'trailing slash in exclude forces directory match (2)' '
137+
138+
git ls-files --others --exclude=one/a.1/ >output &&
139+
grep "^one/a.1" output
140+
141+
'
142+
102143
test_done

unpack-trees.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -521,8 +521,9 @@ static void verify_absent(struct cache_entry *ce, const char *action,
521521

522522
if (!lstat(ce->name, &st)) {
523523
int cnt;
524+
int dtype = ce_to_dtype(ce);
524525

525-
if (o->dir && excluded(o->dir, ce->name))
526+
if (o->dir && excluded(o->dir, ce->name, &dtype))
526527
/*
527528
* ce->name is explicitly excluded, so it is Ok to
528529
* overwrite it.

0 commit comments

Comments
 (0)