@matheusbsbs / matheus-bagliano / commits / d6b8fc303b
Fork 0

gitignore(5): Allow "foo/" in ignore list to match directory "foo"

A pattern "foo/" in the exclude list did not match directory "foo", but a pattern "foo" did. This attempts to extend the exclude mechanism so that it would while not matching a regular file or a symbolic link "foo". In order to differentiate a directory and non directory, this passes down the type of path being checked to excluded() function. A downside is that the recursive directory walk may need to run lstat(2) more often on systems whose "struct dirent" do not give the type of the entry; earlier it did not have to do so for an excluded path, but we now need to figure out if a path is a directory before deciding to exclude it. This is especially bad because an idea similar to the earlier CE_UPTODATE optimization to reduce number of lstat(2) calls would by definition not apply to the codepaths involved, as (1) directories will not be registered in the index, and (2) excluded paths will not be in the index anyway. Signed-off-by: Junio C Hamano <gitster@pobox.com>

Junio C Hamano committed Jan 31, 2008 at 01:17 UTC d6b8fc303b389b026f2bf9918f6f83041488989b
7 files changed +98 -15
Documentation/gitignore.txt
+7
@@ -57,6 +57,13 @@ Patterns have the following format:
57 included again. If a negated pattern matches, this will
58 override lower precedence patterns sources.
59
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 +
67 - If the pattern does not contain a slash '/', git treats it as
68 a shell glob pattern and checks for a match against the
69 pathname without leading directories.
builtin-ls-files.c
+4 -2
@@ -238,7 +238,8 @@ static void show_files(struct dir_struct *dir, const char *prefix)
238 if (show_cached | show_stage) {
239 for (i = 0; i < active_nr; i++) {
240 struct cache_entry *ce = active_cache[i];
241 - if (excluded(dir, ce->name) != dir->show_ignored)
241 + if (excluded(dir, ce->name, ce_to_dtype(ce)) !=
242 + dir->show_ignored)
243 continue;
244 if (show_unmerged && !ce_stage(ce))
245 continue;
@@ -252,7 +253,8 @@ static void show_files(struct dir_struct *dir, const char *prefix)
253 struct cache_entry *ce = active_cache[i];
254 struct stat st;
255 int err;
255 - if (excluded(dir, ce->name) != dir->show_ignored)
256 + if (excluded(dir, ce->name, ce_to_dtype(ce)) !=
257 + dir->show_ignored)
258 continue;
259 err = lstat(ce->name, &st);
260 if (show_deleted && err)
cache.h
+12
@@ -141,6 +141,18 @@ static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned in
141 }
142 return create_ce_mode(mode);
143 }
144 +static inline int ce_to_dtype(const struct cache_entry *ce)
145 +{
146 + unsigned ce_mode = ntohl(ce->ce_mode);
147 + if (S_ISREG(ce_mode))
148 + return DT_REG;
149 + else if (S_ISDIR(ce_mode) || S_ISGITLINK(ce_mode))
150 + return DT_DIR;
151 + else if (S_ISLNK(ce_mode))
152 + return DT_LNK;
153 + else
154 + return DT_UNKNOWN;
155 +}
156 #define canon_mode(mode) \
157 (S_ISREG(mode) ? (S_IFREG | ce_permissions(mode)) : \
158 S_ISLNK(mode) ? S_IFLNK : S_ISDIR(mode) ? S_IFDIR : S_IFGITLINK)
dir.c
+31 -11
@@ -126,18 +126,34 @@ static int no_wildcard(const char *string)
126 void add_exclude(const char *string, const char *base,
127 int baselen, struct exclude_list *which)
128 {
129 - struct exclude *x = xmalloc(sizeof (*x));
129 + struct exclude *x;
130 + size_t len;
131 + int to_exclude = 1;
132 + int flags = 0;
133
131 - x->to_exclude = 1;
134 if (*string == '!') {
133 - x->to_exclude = 0;
135 + to_exclude = 0;
136 string++;
137 }
136 - x->pattern = string;
138 + len = strlen(string);
139 + if (len && string[len - 1] == '/') {
140 + char *s;
141 + x = xmalloc(sizeof(*x) + len);
142 + s = (char*)(x+1);
143 + memcpy(s, string, len - 1);
144 + s[len - 1] = '\0';
145 + string = s;
146 + x->pattern = s;
147 + flags = EXC_FLAG_MUSTBEDIR;
148 + } else {
149 + x = xmalloc(sizeof(*x));
150 + x->pattern = string;
151 + }
152 + x->to_exclude = to_exclude;
153 x->patternlen = strlen(string);
154 x->base = base;
155 x->baselen = baselen;
140 - x->flags = 0;
156 + x->flags = flags;
157 if (!strchr(string, '/'))
158 x->flags |= EXC_FLAG_NODIR;
159 if (no_wildcard(string))
@@ -261,7 +277,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
277 * Return 1 for exclude, 0 for include and -1 for undecided.
278 */
279 static int excluded_1(const char *pathname,
264 - int pathlen, const char *basename,
280 + int pathlen, const char *basename, int dtype,
281 struct exclude_list *el)
282 {
283 int i;
@@ -272,6 +288,10 @@ static int excluded_1(const char *pathname,
288 const char *exclude = x->pattern;
289 int to_exclude = x->to_exclude;
290
291 + if ((x->flags & EXC_FLAG_MUSTBEDIR) &&
292 + (dtype != DT_DIR))
293 + continue;
294 +
295 if (x->flags & EXC_FLAG_NODIR) {
296 /* match basename */
297 if (x->flags & EXC_FLAG_NOWILDCARD) {
@@ -314,7 +334,7 @@ static int excluded_1(const char *pathname,
334 return -1; /* undecided */
335 }
336
317 -int excluded(struct dir_struct *dir, const char *pathname)
337 +int excluded(struct dir_struct *dir, const char *pathname, int dtype)
338 {
339 int pathlen = strlen(pathname);
340 int st;
@@ -323,7 +343,8 @@ int excluded(struct dir_struct *dir, const char *pathname)
343
344 prep_exclude(dir, pathname, basename-pathname);
345 for (st = EXC_CMDL; st <= EXC_FILE; st++) {
326 - switch (excluded_1(pathname, pathlen, basename, &dir->exclude_list[st])) {
346 + switch (excluded_1(pathname, pathlen, basename,
347 + dtype, &dir->exclude_list[st])) {
348 case 0:
349 return 0;
350 case 1:
@@ -560,7 +581,8 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
581 if (simplify_away(fullname, baselen + len, simplify))
582 continue;
583
563 - exclude = excluded(dir, fullname);
584 + dtype = get_dtype(de, fullname);
585 + exclude = excluded(dir, fullname, dtype);
586 if (exclude && dir->collect_ignored
587 && in_pathspec(fullname, baselen + len, simplify))
588 dir_add_ignored(dir, fullname, baselen + len);
@@ -572,8 +594,6 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
594 if (exclude && !dir->show_ignored)
595 continue;
596
575 - dtype = get_dtype(de, fullname);
576 -
597 /*
598 * Do we want to see just the ignored files?
599 * We still need to recurse into directories,
dir.h
+2 -1
@@ -9,6 +9,7 @@ struct dir_entry {
9 #define EXC_FLAG_NODIR 1
10 #define EXC_FLAG_NOWILDCARD 2
11 #define EXC_FLAG_ENDSWITH 4
12 +#define EXC_FLAG_MUSTBEDIR 8
13
14 struct exclude_list {
15 int nr;
@@ -67,7 +68,7 @@ extern int match_pathspec(const char **pathspec, const char *name, int namelen,
68
69 extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen, const char **pathspec);
70
70 -extern int excluded(struct dir_struct *, const char *);
71 +extern int excluded(struct dir_struct *, const char *, int);
72 extern void add_excludes_from_file(struct dir_struct *, const char *fname);
73 extern void add_exclude(const char *string, const char *base,
74 int baselen, struct exclude_list *which);
t/t3001-ls-files-others-exclude.sh
+41
@@ -99,4 +99,45 @@ EOF
99 test_expect_success 'git-status honours core.excludesfile' \
100 'diff -u expect output'
101
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 +
143 test_done
unpack-trees.c
+1 -1
@@ -523,7 +523,7 @@ static void verify_absent(struct cache_entry *ce, const char *action,
523 if (!lstat(ce->name, &st)) {
524 int cnt;
525
526 - if (o->dir && excluded(o->dir, ce->name))
526 + if (o->dir && excluded(o->dir, ce->name, ce_to_dtype(ce)))
527 /*
528 * ce->name is explicitly excluded, so it is Ok to
529 * overwrite it.