line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
/* |
2
|
|
|
|
|
|
|
* Copyright (C) the libgit2 contributors. All rights reserved. |
3
|
|
|
|
|
|
|
* |
4
|
|
|
|
|
|
|
* This file is part of libgit2, distributed under the GNU GPL v2 with |
5
|
|
|
|
|
|
|
* a Linking Exception. For full terms see the included COPYING file. |
6
|
|
|
|
|
|
|
*/ |
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
#include "ignore.h" |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
#include "git2/ignore.h" |
11
|
|
|
|
|
|
|
#include "common.h" |
12
|
|
|
|
|
|
|
#include "attrcache.h" |
13
|
|
|
|
|
|
|
#include "fs_path.h" |
14
|
|
|
|
|
|
|
#include "config.h" |
15
|
|
|
|
|
|
|
#include "wildmatch.h" |
16
|
|
|
|
|
|
|
#include "path.h" |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
#define GIT_IGNORE_INTERNAL "[internal]exclude" |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
#define GIT_IGNORE_DEFAULT_RULES ".\n..\n.git\n" |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
/** |
23
|
|
|
|
|
|
|
* A negative ignore pattern can negate a positive one without |
24
|
|
|
|
|
|
|
* wildcards if it is a basename only and equals the basename of |
25
|
|
|
|
|
|
|
* the positive pattern. Thus |
26
|
|
|
|
|
|
|
* |
27
|
|
|
|
|
|
|
* foo/bar |
28
|
|
|
|
|
|
|
* !bar |
29
|
|
|
|
|
|
|
* |
30
|
|
|
|
|
|
|
* would result in foo/bar being unignored again while |
31
|
|
|
|
|
|
|
* |
32
|
|
|
|
|
|
|
* moo/foo/bar |
33
|
|
|
|
|
|
|
* !foo/bar |
34
|
|
|
|
|
|
|
* |
35
|
|
|
|
|
|
|
* would do nothing. The reverse also holds true: a positive |
36
|
|
|
|
|
|
|
* basename pattern can be negated by unignoring the basename in |
37
|
|
|
|
|
|
|
* subdirectories. Thus |
38
|
|
|
|
|
|
|
* |
39
|
|
|
|
|
|
|
* bar |
40
|
|
|
|
|
|
|
* !foo/bar |
41
|
|
|
|
|
|
|
* |
42
|
|
|
|
|
|
|
* would result in foo/bar being unignored again. As with the |
43
|
|
|
|
|
|
|
* first case, |
44
|
|
|
|
|
|
|
* |
45
|
|
|
|
|
|
|
* foo/bar |
46
|
|
|
|
|
|
|
* !moo/foo/bar |
47
|
|
|
|
|
|
|
* |
48
|
|
|
|
|
|
|
* would do nothing, again. |
49
|
|
|
|
|
|
|
*/ |
50
|
0
|
|
|
|
|
|
static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *neg) |
51
|
|
|
|
|
|
|
{ |
52
|
|
|
|
|
|
|
int (*cmp)(const char *, const char *, size_t); |
53
|
|
|
|
|
|
|
git_attr_fnmatch *longer, *shorter; |
54
|
|
|
|
|
|
|
char *p; |
55
|
|
|
|
|
|
|
|
56
|
0
|
0
|
|
|
|
|
if ((rule->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0 |
57
|
0
|
0
|
|
|
|
|
|| (neg->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0) |
58
|
0
|
|
|
|
|
|
return false; |
59
|
|
|
|
|
|
|
|
60
|
0
|
0
|
|
|
|
|
if (neg->flags & GIT_ATTR_FNMATCH_ICASE) |
61
|
0
|
|
|
|
|
|
cmp = git__strncasecmp; |
62
|
|
|
|
|
|
|
else |
63
|
0
|
|
|
|
|
|
cmp = git__strncmp; |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
/* If lengths match we need to have an exact match */ |
66
|
0
|
0
|
|
|
|
|
if (rule->length == neg->length) { |
67
|
0
|
|
|
|
|
|
return cmp(rule->pattern, neg->pattern, rule->length) == 0; |
68
|
0
|
0
|
|
|
|
|
} else if (rule->length < neg->length) { |
69
|
0
|
|
|
|
|
|
shorter = rule; |
70
|
0
|
|
|
|
|
|
longer = neg; |
71
|
|
|
|
|
|
|
} else { |
72
|
0
|
|
|
|
|
|
shorter = neg; |
73
|
0
|
|
|
|
|
|
longer = rule; |
74
|
|
|
|
|
|
|
} |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
/* Otherwise, we need to check if the shorter |
77
|
|
|
|
|
|
|
* rule is a basename only (that is, it contains |
78
|
|
|
|
|
|
|
* no path separator) and, if so, if it |
79
|
|
|
|
|
|
|
* matches the tail of the longer rule */ |
80
|
0
|
|
|
|
|
|
p = longer->pattern + longer->length - shorter->length; |
81
|
|
|
|
|
|
|
|
82
|
0
|
0
|
|
|
|
|
if (p[-1] != '/') |
83
|
0
|
|
|
|
|
|
return false; |
84
|
0
|
0
|
|
|
|
|
if (memchr(shorter->pattern, '/', shorter->length) != NULL) |
85
|
0
|
|
|
|
|
|
return false; |
86
|
|
|
|
|
|
|
|
87
|
0
|
|
|
|
|
|
return cmp(p, shorter->pattern, shorter->length) == 0; |
88
|
|
|
|
|
|
|
} |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
/** |
91
|
|
|
|
|
|
|
* A negative ignore can only unignore a file which is given explicitly before, thus |
92
|
|
|
|
|
|
|
* |
93
|
|
|
|
|
|
|
* foo |
94
|
|
|
|
|
|
|
* !foo/bar |
95
|
|
|
|
|
|
|
* |
96
|
|
|
|
|
|
|
* does not unignore 'foo/bar' as it's not in the list. However |
97
|
|
|
|
|
|
|
* |
98
|
|
|
|
|
|
|
* foo/ |
99
|
|
|
|
|
|
|
* !foo/bar |
100
|
|
|
|
|
|
|
* |
101
|
|
|
|
|
|
|
* does unignore 'foo/bar', as it is contained within the 'foo/' rule. |
102
|
|
|
|
|
|
|
*/ |
103
|
0
|
|
|
|
|
|
static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match) |
104
|
|
|
|
|
|
|
{ |
105
|
0
|
|
|
|
|
|
int error = 0, wildmatch_flags, effective_flags; |
106
|
|
|
|
|
|
|
size_t i; |
107
|
|
|
|
|
|
|
git_attr_fnmatch *rule; |
108
|
|
|
|
|
|
|
char *path; |
109
|
0
|
|
|
|
|
|
git_str buf = GIT_STR_INIT; |
110
|
|
|
|
|
|
|
|
111
|
0
|
|
|
|
|
|
*out = 0; |
112
|
|
|
|
|
|
|
|
113
|
0
|
|
|
|
|
|
wildmatch_flags = WM_PATHNAME; |
114
|
0
|
0
|
|
|
|
|
if (match->flags & GIT_ATTR_FNMATCH_ICASE) |
115
|
0
|
|
|
|
|
|
wildmatch_flags |= WM_CASEFOLD; |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
/* path of the file relative to the workdir, so we match the rules in subdirs */ |
118
|
0
|
0
|
|
|
|
|
if (match->containing_dir) { |
119
|
0
|
|
|
|
|
|
git_str_puts(&buf, match->containing_dir); |
120
|
|
|
|
|
|
|
} |
121
|
0
|
0
|
|
|
|
|
if (git_str_puts(&buf, match->pattern) < 0) |
122
|
0
|
|
|
|
|
|
return -1; |
123
|
|
|
|
|
|
|
|
124
|
0
|
|
|
|
|
|
path = git_str_detach(&buf); |
125
|
|
|
|
|
|
|
|
126
|
0
|
0
|
|
|
|
|
git_vector_foreach(rules, i, rule) { |
127
|
0
|
0
|
|
|
|
|
if (!(rule->flags & GIT_ATTR_FNMATCH_HASWILD)) { |
128
|
0
|
0
|
|
|
|
|
if (does_negate_pattern(rule, match)) { |
129
|
0
|
|
|
|
|
|
error = 0; |
130
|
0
|
|
|
|
|
|
*out = 1; |
131
|
0
|
|
|
|
|
|
goto out; |
132
|
|
|
|
|
|
|
} |
133
|
|
|
|
|
|
|
else |
134
|
0
|
|
|
|
|
|
continue; |
135
|
|
|
|
|
|
|
} |
136
|
|
|
|
|
|
|
|
137
|
0
|
|
|
|
|
|
git_str_clear(&buf); |
138
|
0
|
0
|
|
|
|
|
if (rule->containing_dir) |
139
|
0
|
|
|
|
|
|
git_str_puts(&buf, rule->containing_dir); |
140
|
0
|
|
|
|
|
|
git_str_puts(&buf, rule->pattern); |
141
|
|
|
|
|
|
|
|
142
|
0
|
0
|
|
|
|
|
if (git_str_oom(&buf)) |
143
|
0
|
|
|
|
|
|
goto out; |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
/* |
146
|
|
|
|
|
|
|
* if rule isn't for full path we match without PATHNAME flag |
147
|
|
|
|
|
|
|
* as lines like *.txt should match something like dir/test.txt |
148
|
|
|
|
|
|
|
* requiring * to also match / |
149
|
|
|
|
|
|
|
*/ |
150
|
0
|
|
|
|
|
|
effective_flags = wildmatch_flags; |
151
|
0
|
0
|
|
|
|
|
if (!(rule->flags & GIT_ATTR_FNMATCH_FULLPATH)) |
152
|
0
|
|
|
|
|
|
effective_flags &= ~WM_PATHNAME; |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
/* if we found a match, we want to keep this rule */ |
155
|
0
|
0
|
|
|
|
|
if ((wildmatch(git_str_cstr(&buf), path, effective_flags)) == WM_MATCH) { |
156
|
0
|
|
|
|
|
|
*out = 1; |
157
|
0
|
|
|
|
|
|
error = 0; |
158
|
0
|
|
|
|
|
|
goto out; |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
} |
161
|
|
|
|
|
|
|
|
162
|
0
|
|
|
|
|
|
error = 0; |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
out: |
165
|
0
|
|
|
|
|
|
git__free(path); |
166
|
0
|
|
|
|
|
|
git_str_dispose(&buf); |
167
|
0
|
|
|
|
|
|
return error; |
168
|
|
|
|
|
|
|
} |
169
|
|
|
|
|
|
|
|
170
|
369
|
|
|
|
|
|
static int parse_ignore_file( |
171
|
|
|
|
|
|
|
git_repository *repo, git_attr_file *attrs, const char *data, bool allow_macros) |
172
|
|
|
|
|
|
|
{ |
173
|
369
|
|
|
|
|
|
int error = 0; |
174
|
369
|
|
|
|
|
|
int ignore_case = false; |
175
|
369
|
|
|
|
|
|
const char *scan = data, *context = NULL; |
176
|
369
|
|
|
|
|
|
git_attr_fnmatch *match = NULL; |
177
|
|
|
|
|
|
|
|
178
|
369
|
|
|
|
|
|
GIT_UNUSED(allow_macros); |
179
|
|
|
|
|
|
|
|
180
|
369
|
50
|
|
|
|
|
if (git_repository__configmap_lookup(&ignore_case, repo, GIT_CONFIGMAP_IGNORECASE) < 0) |
181
|
0
|
|
|
|
|
|
git_error_clear(); |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
/* if subdir file path, convert context for file paths */ |
184
|
738
|
|
|
|
|
|
if (attrs->entry && |
185
|
736
|
100
|
|
|
|
|
git_fs_path_root(attrs->entry->path) < 0 && |
186
|
367
|
|
|
|
|
|
!git__suffixcmp(attrs->entry->path, "/" GIT_IGNORE_FILE)) |
187
|
176
|
|
|
|
|
|
context = attrs->entry->path; |
188
|
|
|
|
|
|
|
|
189
|
369
|
50
|
|
|
|
|
if (git_mutex_lock(&attrs->lock) < 0) { |
190
|
0
|
|
|
|
|
|
git_error_set(GIT_ERROR_OS, "failed to lock ignore file"); |
191
|
0
|
|
|
|
|
|
return -1; |
192
|
|
|
|
|
|
|
} |
193
|
|
|
|
|
|
|
|
194
|
441
|
50
|
|
|
|
|
while (!error && *scan) { |
|
|
100
|
|
|
|
|
|
195
|
72
|
|
|
|
|
|
int valid_rule = 1; |
196
|
|
|
|
|
|
|
|
197
|
72
|
100
|
|
|
|
|
if (!match && !(match = git__calloc(1, sizeof(*match)))) { |
|
|
50
|
|
|
|
|
|
198
|
0
|
|
|
|
|
|
error = -1; |
199
|
0
|
|
|
|
|
|
break; |
200
|
|
|
|
|
|
|
} |
201
|
|
|
|
|
|
|
|
202
|
72
|
|
|
|
|
|
match->flags = |
203
|
|
|
|
|
|
|
GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG; |
204
|
|
|
|
|
|
|
|
205
|
72
|
100
|
|
|
|
|
if (!(error = git_attr_fnmatch__parse( |
206
|
|
|
|
|
|
|
match, &attrs->pool, context, &scan))) |
207
|
|
|
|
|
|
|
{ |
208
|
44
|
|
|
|
|
|
match->flags |= GIT_ATTR_FNMATCH_IGNORE; |
209
|
|
|
|
|
|
|
|
210
|
44
|
50
|
|
|
|
|
if (ignore_case) |
211
|
0
|
|
|
|
|
|
match->flags |= GIT_ATTR_FNMATCH_ICASE; |
212
|
|
|
|
|
|
|
|
213
|
44
|
|
|
|
|
|
scan = git__next_line(scan); |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
/* |
216
|
|
|
|
|
|
|
* If a negative match doesn't actually do anything, |
217
|
|
|
|
|
|
|
* throw it away. As we cannot always verify whether a |
218
|
|
|
|
|
|
|
* rule containing wildcards negates another rule, we |
219
|
|
|
|
|
|
|
* do not optimize away these rules, though. |
220
|
|
|
|
|
|
|
* */ |
221
|
44
|
50
|
|
|
|
|
if (match->flags & GIT_ATTR_FNMATCH_NEGATIVE |
222
|
0
|
0
|
|
|
|
|
&& !(match->flags & GIT_ATTR_FNMATCH_HASWILD)) |
223
|
0
|
|
|
|
|
|
error = does_negate_rule(&valid_rule, &attrs->rules, match); |
224
|
|
|
|
|
|
|
|
225
|
44
|
50
|
|
|
|
|
if (!error && valid_rule) |
|
|
50
|
|
|
|
|
|
226
|
44
|
|
|
|
|
|
error = git_vector_insert(&attrs->rules, match); |
227
|
|
|
|
|
|
|
} |
228
|
|
|
|
|
|
|
|
229
|
72
|
100
|
|
|
|
|
if (error != 0 || !valid_rule) { |
|
|
50
|
|
|
|
|
|
230
|
28
|
|
|
|
|
|
match->pattern = NULL; |
231
|
|
|
|
|
|
|
|
232
|
56
|
50
|
|
|
|
|
if (error == GIT_ENOTFOUND) |
233
|
28
|
|
|
|
|
|
error = 0; |
234
|
|
|
|
|
|
|
} else { |
235
|
72
|
|
|
|
|
|
match = NULL; /* vector now "owns" the match */ |
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
} |
238
|
|
|
|
|
|
|
|
239
|
369
|
|
|
|
|
|
git_mutex_unlock(&attrs->lock); |
240
|
369
|
|
|
|
|
|
git__free(match); |
241
|
|
|
|
|
|
|
|
242
|
369
|
|
|
|
|
|
return error; |
243
|
|
|
|
|
|
|
} |
244
|
|
|
|
|
|
|
|
245
|
540
|
|
|
|
|
|
static int push_ignore_file( |
246
|
|
|
|
|
|
|
git_ignores *ignores, |
247
|
|
|
|
|
|
|
git_vector *which_list, |
248
|
|
|
|
|
|
|
const char *base, |
249
|
|
|
|
|
|
|
const char *filename) |
250
|
|
|
|
|
|
|
{ |
251
|
540
|
|
|
|
|
|
git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_FILE, base, filename }; |
252
|
540
|
|
|
|
|
|
git_attr_file *file = NULL; |
253
|
540
|
|
|
|
|
|
int error = 0; |
254
|
|
|
|
|
|
|
|
255
|
540
|
|
|
|
|
|
error = git_attr_cache__get(&file, ignores->repo, NULL, &source, parse_ignore_file, false); |
256
|
|
|
|
|
|
|
|
257
|
540
|
50
|
|
|
|
|
if (error < 0) |
258
|
0
|
|
|
|
|
|
return error; |
259
|
|
|
|
|
|
|
|
260
|
540
|
50
|
|
|
|
|
if (file != NULL) { |
261
|
540
|
50
|
|
|
|
|
if ((error = git_vector_insert(which_list, file)) < 0) |
262
|
0
|
|
|
|
|
|
git_attr_file__free(file); |
263
|
|
|
|
|
|
|
} |
264
|
|
|
|
|
|
|
|
265
|
540
|
|
|
|
|
|
return error; |
266
|
|
|
|
|
|
|
} |
267
|
|
|
|
|
|
|
|
268
|
182
|
|
|
|
|
|
static int push_one_ignore(void *payload, const char *path) |
269
|
|
|
|
|
|
|
{ |
270
|
182
|
|
|
|
|
|
git_ignores *ign = payload; |
271
|
182
|
|
|
|
|
|
ign->depth++; |
272
|
182
|
|
|
|
|
|
return push_ignore_file(ign, &ign->ign_path, path, GIT_IGNORE_FILE); |
273
|
|
|
|
|
|
|
} |
274
|
|
|
|
|
|
|
|
275
|
184
|
|
|
|
|
|
static int get_internal_ignores(git_attr_file **out, git_repository *repo) |
276
|
|
|
|
|
|
|
{ |
277
|
184
|
|
|
|
|
|
git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_MEMORY, NULL, GIT_IGNORE_INTERNAL }; |
278
|
|
|
|
|
|
|
int error; |
279
|
|
|
|
|
|
|
|
280
|
184
|
50
|
|
|
|
|
if ((error = git_attr_cache__init(repo)) < 0) |
281
|
0
|
|
|
|
|
|
return error; |
282
|
|
|
|
|
|
|
|
283
|
184
|
|
|
|
|
|
error = git_attr_cache__get(out, repo, NULL, &source, NULL, false); |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
/* if internal rules list is empty, insert default rules */ |
286
|
184
|
50
|
|
|
|
|
if (!error && !(*out)->rules.length) |
|
|
100
|
|
|
|
|
|
287
|
14
|
|
|
|
|
|
error = parse_ignore_file(repo, *out, GIT_IGNORE_DEFAULT_RULES, false); |
288
|
|
|
|
|
|
|
|
289
|
184
|
|
|
|
|
|
return error; |
290
|
|
|
|
|
|
|
} |
291
|
|
|
|
|
|
|
|
292
|
182
|
|
|
|
|
|
int git_ignore__for_path( |
293
|
|
|
|
|
|
|
git_repository *repo, |
294
|
|
|
|
|
|
|
const char *path, |
295
|
|
|
|
|
|
|
git_ignores *ignores) |
296
|
|
|
|
|
|
|
{ |
297
|
182
|
|
|
|
|
|
int error = 0; |
298
|
182
|
|
|
|
|
|
const char *workdir = git_repository_workdir(repo); |
299
|
182
|
|
|
|
|
|
git_str infopath = GIT_STR_INIT; |
300
|
|
|
|
|
|
|
|
301
|
182
|
50
|
|
|
|
|
GIT_ASSERT_ARG(repo); |
302
|
182
|
50
|
|
|
|
|
GIT_ASSERT_ARG(ignores); |
303
|
182
|
50
|
|
|
|
|
GIT_ASSERT_ARG(path); |
304
|
|
|
|
|
|
|
|
305
|
182
|
|
|
|
|
|
memset(ignores, 0, sizeof(*ignores)); |
306
|
182
|
|
|
|
|
|
ignores->repo = repo; |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
/* Read the ignore_case flag */ |
309
|
182
|
50
|
|
|
|
|
if ((error = git_repository__configmap_lookup( |
310
|
|
|
|
|
|
|
&ignores->ignore_case, repo, GIT_CONFIGMAP_IGNORECASE)) < 0) |
311
|
0
|
|
|
|
|
|
goto cleanup; |
312
|
|
|
|
|
|
|
|
313
|
182
|
50
|
|
|
|
|
if ((error = git_attr_cache__init(repo)) < 0) |
314
|
0
|
|
|
|
|
|
goto cleanup; |
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
/* given a unrooted path in a non-bare repo, resolve it */ |
317
|
364
|
50
|
|
|
|
|
if (workdir && git_fs_path_root(path) < 0) { |
|
|
50
|
|
|
|
|
|
318
|
182
|
|
|
|
|
|
git_str local = GIT_STR_INIT; |
319
|
|
|
|
|
|
|
|
320
|
182
|
50
|
|
|
|
|
if ((error = git_fs_path_dirname_r(&local, path)) < 0 || |
|
|
50
|
|
|
|
|
|
321
|
182
|
50
|
|
|
|
|
(error = git_fs_path_resolve_relative(&local, 0)) < 0 || |
322
|
182
|
50
|
|
|
|
|
(error = git_fs_path_to_dir(&local)) < 0 || |
323
|
182
|
|
|
|
|
|
(error = git_str_joinpath(&ignores->dir, workdir, local.ptr)) < 0 || |
324
|
182
|
|
|
|
|
|
(error = git_path_validate_str_length(repo, &ignores->dir)) < 0) { |
325
|
|
|
|
|
|
|
/* Nothing, we just want to stop on the first error */ |
326
|
|
|
|
|
|
|
} |
327
|
|
|
|
|
|
|
|
328
|
182
|
|
|
|
|
|
git_str_dispose(&local); |
329
|
|
|
|
|
|
|
} else { |
330
|
0
|
0
|
|
|
|
|
if (!(error = git_str_joinpath(&ignores->dir, path, ""))) |
331
|
0
|
|
|
|
|
|
error = git_path_validate_str_length(NULL, &ignores->dir); |
332
|
|
|
|
|
|
|
} |
333
|
|
|
|
|
|
|
|
334
|
182
|
50
|
|
|
|
|
if (error < 0) |
335
|
0
|
|
|
|
|
|
goto cleanup; |
336
|
|
|
|
|
|
|
|
337
|
182
|
50
|
|
|
|
|
if (workdir && !git__prefixcmp(ignores->dir.ptr, workdir)) |
|
|
50
|
|
|
|
|
|
338
|
182
|
|
|
|
|
|
ignores->dir_root = strlen(workdir); |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
/* set up internals */ |
341
|
182
|
50
|
|
|
|
|
if ((error = get_internal_ignores(&ignores->ign_internal, repo)) < 0) |
342
|
0
|
|
|
|
|
|
goto cleanup; |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
/* load .gitignore up the path */ |
345
|
182
|
50
|
|
|
|
|
if (workdir != NULL) { |
346
|
182
|
|
|
|
|
|
error = git_fs_path_walk_up( |
347
|
|
|
|
|
|
|
&ignores->dir, workdir, push_one_ignore, ignores); |
348
|
182
|
50
|
|
|
|
|
if (error < 0) |
349
|
0
|
|
|
|
|
|
goto cleanup; |
350
|
|
|
|
|
|
|
} |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
/* load .git/info/exclude if possible */ |
353
|
182
|
50
|
|
|
|
|
if ((error = git_repository__item_path(&infopath, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 || |
|
|
50
|
|
|
|
|
|
354
|
182
|
|
|
|
|
|
(error = push_ignore_file(ignores, &ignores->ign_global, infopath.ptr, GIT_IGNORE_FILE_INREPO)) < 0) { |
355
|
0
|
0
|
|
|
|
|
if (error != GIT_ENOTFOUND) |
356
|
0
|
|
|
|
|
|
goto cleanup; |
357
|
0
|
|
|
|
|
|
error = 0; |
358
|
|
|
|
|
|
|
} |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
/* load core.excludesfile */ |
361
|
182
|
50
|
|
|
|
|
if (git_repository_attr_cache(repo)->cfg_excl_file != NULL) |
362
|
0
|
|
|
|
|
|
error = push_ignore_file( |
363
|
|
|
|
|
|
|
ignores, &ignores->ign_global, NULL, |
364
|
0
|
|
|
|
|
|
git_repository_attr_cache(repo)->cfg_excl_file); |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
cleanup: |
367
|
182
|
|
|
|
|
|
git_str_dispose(&infopath); |
368
|
182
|
50
|
|
|
|
|
if (error < 0) |
369
|
0
|
|
|
|
|
|
git_ignore__free(ignores); |
370
|
|
|
|
|
|
|
|
371
|
182
|
|
|
|
|
|
return error; |
372
|
|
|
|
|
|
|
} |
373
|
|
|
|
|
|
|
|
374
|
176
|
|
|
|
|
|
int git_ignore__push_dir(git_ignores *ign, const char *dir) |
375
|
|
|
|
|
|
|
{ |
376
|
176
|
50
|
|
|
|
|
if (git_str_joinpath(&ign->dir, ign->dir.ptr, dir) < 0) |
377
|
0
|
|
|
|
|
|
return -1; |
378
|
|
|
|
|
|
|
|
379
|
176
|
|
|
|
|
|
ign->depth++; |
380
|
|
|
|
|
|
|
|
381
|
176
|
|
|
|
|
|
return push_ignore_file( |
382
|
176
|
|
|
|
|
|
ign, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE); |
383
|
|
|
|
|
|
|
} |
384
|
|
|
|
|
|
|
|
385
|
355
|
|
|
|
|
|
int git_ignore__pop_dir(git_ignores *ign) |
386
|
|
|
|
|
|
|
{ |
387
|
355
|
50
|
|
|
|
|
if (ign->ign_path.length > 0) { |
388
|
355
|
|
|
|
|
|
git_attr_file *file = git_vector_last(&ign->ign_path); |
389
|
355
|
|
|
|
|
|
const char *start = file->entry->path, *end; |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
/* - ign->dir looks something like "/home/user/a/b/" (or "a/b/c/d/") |
392
|
|
|
|
|
|
|
* - file->path looks something like "a/b/.gitignore |
393
|
|
|
|
|
|
|
* |
394
|
|
|
|
|
|
|
* We are popping the last directory off ign->dir. We also want |
395
|
|
|
|
|
|
|
* to remove the file from the vector if the popped directory |
396
|
|
|
|
|
|
|
* matches the ignore path. We need to test if the "a/b" part of |
397
|
|
|
|
|
|
|
* the file key matches the path we are about to pop. |
398
|
|
|
|
|
|
|
*/ |
399
|
|
|
|
|
|
|
|
400
|
355
|
100
|
|
|
|
|
if ((end = strrchr(start, '/')) != NULL) { |
401
|
176
|
|
|
|
|
|
size_t dirlen = (end - start) + 1; |
402
|
176
|
|
|
|
|
|
const char *relpath = ign->dir.ptr + ign->dir_root; |
403
|
176
|
|
|
|
|
|
size_t pathlen = ign->dir.size - ign->dir_root; |
404
|
|
|
|
|
|
|
|
405
|
176
|
50
|
|
|
|
|
if (pathlen == dirlen && !memcmp(relpath, start, dirlen)) { |
|
|
50
|
|
|
|
|
|
406
|
176
|
|
|
|
|
|
git_vector_pop(&ign->ign_path); |
407
|
176
|
|
|
|
|
|
git_attr_file__free(file); |
408
|
|
|
|
|
|
|
} |
409
|
|
|
|
|
|
|
} |
410
|
|
|
|
|
|
|
} |
411
|
|
|
|
|
|
|
|
412
|
355
|
100
|
|
|
|
|
if (--ign->depth > 0) { |
413
|
176
|
|
|
|
|
|
git_str_rtruncate_at_char(&ign->dir, '/'); |
414
|
176
|
|
|
|
|
|
git_fs_path_to_dir(&ign->dir); |
415
|
|
|
|
|
|
|
} |
416
|
|
|
|
|
|
|
|
417
|
355
|
|
|
|
|
|
return 0; |
418
|
|
|
|
|
|
|
} |
419
|
|
|
|
|
|
|
|
420
|
213
|
|
|
|
|
|
void git_ignore__free(git_ignores *ignores) |
421
|
|
|
|
|
|
|
{ |
422
|
|
|
|
|
|
|
unsigned int i; |
423
|
|
|
|
|
|
|
git_attr_file *file; |
424
|
|
|
|
|
|
|
|
425
|
213
|
|
|
|
|
|
git_attr_file__free(ignores->ign_internal); |
426
|
|
|
|
|
|
|
|
427
|
395
|
100
|
|
|
|
|
git_vector_foreach(&ignores->ign_path, i, file) { |
428
|
182
|
|
|
|
|
|
git_attr_file__free(file); |
429
|
182
|
|
|
|
|
|
ignores->ign_path.contents[i] = NULL; |
430
|
|
|
|
|
|
|
} |
431
|
213
|
|
|
|
|
|
git_vector_free(&ignores->ign_path); |
432
|
|
|
|
|
|
|
|
433
|
395
|
100
|
|
|
|
|
git_vector_foreach(&ignores->ign_global, i, file) { |
434
|
182
|
|
|
|
|
|
git_attr_file__free(file); |
435
|
182
|
|
|
|
|
|
ignores->ign_global.contents[i] = NULL; |
436
|
|
|
|
|
|
|
} |
437
|
213
|
|
|
|
|
|
git_vector_free(&ignores->ign_global); |
438
|
|
|
|
|
|
|
|
439
|
213
|
|
|
|
|
|
git_str_dispose(&ignores->dir); |
440
|
213
|
|
|
|
|
|
} |
441
|
|
|
|
|
|
|
|
442
|
2677
|
|
|
|
|
|
static bool ignore_lookup_in_rules( |
443
|
|
|
|
|
|
|
int *ignored, git_attr_file *file, git_attr_path *path) |
444
|
|
|
|
|
|
|
{ |
445
|
|
|
|
|
|
|
size_t j; |
446
|
|
|
|
|
|
|
git_attr_fnmatch *match; |
447
|
|
|
|
|
|
|
|
448
|
4996
|
100
|
|
|
|
|
git_vector_rforeach(&file->rules, j, match) { |
449
|
2330
|
50
|
|
|
|
|
if (match->flags & GIT_ATTR_FNMATCH_DIRECTORY && |
|
|
0
|
|
|
|
|
|
450
|
0
|
|
|
|
|
|
path->is_dir == GIT_DIR_FLAG_FALSE) |
451
|
0
|
|
|
|
|
|
continue; |
452
|
2330
|
100
|
|
|
|
|
if (git_attr_fnmatch__match(match, path)) { |
453
|
22
|
|
|
|
|
|
*ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0) ? |
454
|
11
|
|
|
|
|
|
GIT_IGNORE_TRUE : GIT_IGNORE_FALSE; |
455
|
11
|
|
|
|
|
|
return true; |
456
|
|
|
|
|
|
|
} |
457
|
|
|
|
|
|
|
} |
458
|
|
|
|
|
|
|
|
459
|
2666
|
|
|
|
|
|
return false; |
460
|
|
|
|
|
|
|
} |
461
|
|
|
|
|
|
|
|
462
|
760
|
|
|
|
|
|
int git_ignore__lookup( |
463
|
|
|
|
|
|
|
int *out, git_ignores *ignores, const char *pathname, git_dir_flag dir_flag) |
464
|
|
|
|
|
|
|
{ |
465
|
|
|
|
|
|
|
size_t i; |
466
|
|
|
|
|
|
|
git_attr_file *file; |
467
|
|
|
|
|
|
|
git_attr_path path; |
468
|
|
|
|
|
|
|
|
469
|
760
|
|
|
|
|
|
*out = GIT_IGNORE_NOTFOUND; |
470
|
|
|
|
|
|
|
|
471
|
760
|
50
|
|
|
|
|
if (git_attr_path__init( |
472
|
760
|
|
|
|
|
|
&path, pathname, git_repository_workdir(ignores->repo), dir_flag) < 0) |
473
|
0
|
|
|
|
|
|
return -1; |
474
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
/* first process builtins - success means path was found */ |
476
|
760
|
100
|
|
|
|
|
if (ignore_lookup_in_rules(out, ignores->ign_internal, &path)) |
477
|
10
|
|
|
|
|
|
goto cleanup; |
478
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
/* next process files in the path. |
480
|
|
|
|
|
|
|
* this process has to process ignores in reverse order |
481
|
|
|
|
|
|
|
* to ensure correct prioritization of rules |
482
|
|
|
|
|
|
|
*/ |
483
|
1910
|
100
|
|
|
|
|
git_vector_rforeach(&ignores->ign_path, i, file) { |
484
|
1160
|
50
|
|
|
|
|
if (ignore_lookup_in_rules(out, file, &path)) |
485
|
0
|
|
|
|
|
|
goto cleanup; |
486
|
|
|
|
|
|
|
} |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
/* last process global ignores */ |
489
|
1500
|
100
|
|
|
|
|
git_vector_foreach(&ignores->ign_global, i, file) { |
490
|
750
|
50
|
|
|
|
|
if (ignore_lookup_in_rules(out, file, &path)) |
491
|
0
|
|
|
|
|
|
goto cleanup; |
492
|
|
|
|
|
|
|
} |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
cleanup: |
495
|
760
|
|
|
|
|
|
git_attr_path__free(&path); |
496
|
760
|
|
|
|
|
|
return 0; |
497
|
|
|
|
|
|
|
} |
498
|
|
|
|
|
|
|
|
499
|
2
|
|
|
|
|
|
int git_ignore_add_rule(git_repository *repo, const char *rules) |
500
|
|
|
|
|
|
|
{ |
501
|
|
|
|
|
|
|
int error; |
502
|
2
|
|
|
|
|
|
git_attr_file *ign_internal = NULL; |
503
|
|
|
|
|
|
|
|
504
|
2
|
50
|
|
|
|
|
if ((error = get_internal_ignores(&ign_internal, repo)) < 0) |
505
|
0
|
|
|
|
|
|
return error; |
506
|
|
|
|
|
|
|
|
507
|
2
|
|
|
|
|
|
error = parse_ignore_file(repo, ign_internal, rules, false); |
508
|
2
|
|
|
|
|
|
git_attr_file__free(ign_internal); |
509
|
|
|
|
|
|
|
|
510
|
2
|
|
|
|
|
|
return error; |
511
|
|
|
|
|
|
|
} |
512
|
|
|
|
|
|
|
|
513
|
0
|
|
|
|
|
|
int git_ignore_clear_internal_rules(git_repository *repo) |
514
|
|
|
|
|
|
|
{ |
515
|
|
|
|
|
|
|
int error; |
516
|
|
|
|
|
|
|
git_attr_file *ign_internal; |
517
|
|
|
|
|
|
|
|
518
|
0
|
0
|
|
|
|
|
if ((error = get_internal_ignores(&ign_internal, repo)) < 0) |
519
|
0
|
|
|
|
|
|
return error; |
520
|
|
|
|
|
|
|
|
521
|
0
|
0
|
|
|
|
|
if (!(error = git_attr_file__clear_rules(ign_internal, true))) |
522
|
0
|
|
|
|
|
|
error = parse_ignore_file( |
523
|
|
|
|
|
|
|
repo, ign_internal, GIT_IGNORE_DEFAULT_RULES, false); |
524
|
|
|
|
|
|
|
|
525
|
0
|
|
|
|
|
|
git_attr_file__free(ign_internal); |
526
|
0
|
|
|
|
|
|
return error; |
527
|
|
|
|
|
|
|
} |
528
|
|
|
|
|
|
|
|
529
|
3
|
|
|
|
|
|
int git_ignore_path_is_ignored( |
530
|
|
|
|
|
|
|
int *ignored, |
531
|
|
|
|
|
|
|
git_repository *repo, |
532
|
|
|
|
|
|
|
const char *pathname) |
533
|
|
|
|
|
|
|
{ |
534
|
|
|
|
|
|
|
int error; |
535
|
|
|
|
|
|
|
const char *workdir; |
536
|
|
|
|
|
|
|
git_attr_path path; |
537
|
|
|
|
|
|
|
git_ignores ignores; |
538
|
|
|
|
|
|
|
unsigned int i; |
539
|
|
|
|
|
|
|
git_attr_file *file; |
540
|
3
|
|
|
|
|
|
git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN; |
541
|
|
|
|
|
|
|
|
542
|
3
|
50
|
|
|
|
|
GIT_ASSERT_ARG(repo); |
543
|
3
|
50
|
|
|
|
|
GIT_ASSERT_ARG(ignored); |
544
|
3
|
50
|
|
|
|
|
GIT_ASSERT_ARG(pathname); |
545
|
|
|
|
|
|
|
|
546
|
3
|
|
|
|
|
|
workdir = git_repository_workdir(repo); |
547
|
|
|
|
|
|
|
|
548
|
3
|
|
|
|
|
|
memset(&path, 0, sizeof(path)); |
549
|
3
|
|
|
|
|
|
memset(&ignores, 0, sizeof(ignores)); |
550
|
|
|
|
|
|
|
|
551
|
3
|
50
|
|
|
|
|
if (!git__suffixcmp(pathname, "/")) |
552
|
0
|
|
|
|
|
|
dir_flag = GIT_DIR_FLAG_TRUE; |
553
|
3
|
50
|
|
|
|
|
else if (git_repository_is_bare(repo)) |
554
|
0
|
|
|
|
|
|
dir_flag = GIT_DIR_FLAG_FALSE; |
555
|
|
|
|
|
|
|
|
556
|
3
|
50
|
|
|
|
|
if ((error = git_attr_path__init(&path, pathname, workdir, dir_flag)) < 0 || |
|
|
50
|
|
|
|
|
|
557
|
3
|
|
|
|
|
|
(error = git_ignore__for_path(repo, path.path, &ignores)) < 0) |
558
|
|
|
|
|
|
|
goto cleanup; |
559
|
|
|
|
|
|
|
|
560
|
|
|
|
|
|
|
while (1) { |
561
|
|
|
|
|
|
|
/* first process builtins - success means path was found */ |
562
|
3
|
100
|
|
|
|
|
if (ignore_lookup_in_rules(ignored, ignores.ign_internal, &path)) |
563
|
1
|
|
|
|
|
|
goto cleanup; |
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
/* next process files in the path */ |
566
|
4
|
100
|
|
|
|
|
git_vector_foreach(&ignores.ign_path, i, file) { |
567
|
2
|
50
|
|
|
|
|
if (ignore_lookup_in_rules(ignored, file, &path)) |
568
|
0
|
|
|
|
|
|
goto cleanup; |
569
|
|
|
|
|
|
|
} |
570
|
|
|
|
|
|
|
|
571
|
|
|
|
|
|
|
/* last process global ignores */ |
572
|
4
|
100
|
|
|
|
|
git_vector_foreach(&ignores.ign_global, i, file) { |
573
|
2
|
50
|
|
|
|
|
if (ignore_lookup_in_rules(ignored, file, &path)) |
574
|
0
|
|
|
|
|
|
goto cleanup; |
575
|
|
|
|
|
|
|
} |
576
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
/* move up one directory */ |
578
|
2
|
50
|
|
|
|
|
if (path.basename == path.path) |
579
|
2
|
|
|
|
|
|
break; |
580
|
0
|
|
|
|
|
|
path.basename[-1] = '\0'; |
581
|
0
|
0
|
|
|
|
|
while (path.basename > path.path && *path.basename != '/') |
|
|
0
|
|
|
|
|
|
582
|
0
|
|
|
|
|
|
path.basename--; |
583
|
0
|
0
|
|
|
|
|
if (path.basename > path.path) |
584
|
0
|
|
|
|
|
|
path.basename++; |
585
|
0
|
|
|
|
|
|
path.is_dir = 1; |
586
|
|
|
|
|
|
|
|
587
|
0
|
0
|
|
|
|
|
if ((error = git_ignore__pop_dir(&ignores)) < 0) |
588
|
0
|
|
|
|
|
|
break; |
589
|
0
|
|
|
|
|
|
} |
590
|
|
|
|
|
|
|
|
591
|
2
|
|
|
|
|
|
*ignored = 0; |
592
|
|
|
|
|
|
|
|
593
|
|
|
|
|
|
|
cleanup: |
594
|
3
|
|
|
|
|
|
git_attr_path__free(&path); |
595
|
3
|
|
|
|
|
|
git_ignore__free(&ignores); |
596
|
3
|
|
|
|
|
|
return error; |
597
|
|
|
|
|
|
|
} |
598
|
|
|
|
|
|
|
|
599
|
0
|
|
|
|
|
|
int git_ignore__check_pathspec_for_exact_ignores( |
600
|
|
|
|
|
|
|
git_repository *repo, |
601
|
|
|
|
|
|
|
git_vector *vspec, |
602
|
|
|
|
|
|
|
bool no_fnmatch) |
603
|
|
|
|
|
|
|
{ |
604
|
0
|
|
|
|
|
|
int error = 0; |
605
|
|
|
|
|
|
|
size_t i; |
606
|
|
|
|
|
|
|
git_attr_fnmatch *match; |
607
|
|
|
|
|
|
|
int ignored; |
608
|
0
|
|
|
|
|
|
git_str path = GIT_STR_INIT; |
609
|
|
|
|
|
|
|
const char *filename; |
610
|
|
|
|
|
|
|
git_index *idx; |
611
|
|
|
|
|
|
|
|
612
|
0
|
0
|
|
|
|
|
if ((error = git_repository__ensure_not_bare( |
613
|
0
|
0
|
|
|
|
|
repo, "validate pathspec")) < 0 || |
614
|
|
|
|
|
|
|
(error = git_repository_index(&idx, repo)) < 0) |
615
|
0
|
|
|
|
|
|
return error; |
616
|
|
|
|
|
|
|
|
617
|
0
|
0
|
|
|
|
|
git_vector_foreach(vspec, i, match) { |
618
|
|
|
|
|
|
|
/* skip wildcard matches (if they are being used) */ |
619
|
0
|
0
|
|
|
|
|
if ((match->flags & GIT_ATTR_FNMATCH_HASWILD) != 0 && |
|
|
0
|
|
|
|
|
|
620
|
0
|
|
|
|
|
|
!no_fnmatch) |
621
|
0
|
|
|
|
|
|
continue; |
622
|
|
|
|
|
|
|
|
623
|
0
|
|
|
|
|
|
filename = match->pattern; |
624
|
|
|
|
|
|
|
|
625
|
|
|
|
|
|
|
/* if file is already in the index, it's fine */ |
626
|
0
|
0
|
|
|
|
|
if (git_index_get_bypath(idx, filename, 0) != NULL) |
627
|
0
|
|
|
|
|
|
continue; |
628
|
|
|
|
|
|
|
|
629
|
0
|
0
|
|
|
|
|
if ((error = git_repository_workdir_path(&path, repo, filename)) < 0) |
630
|
0
|
|
|
|
|
|
break; |
631
|
|
|
|
|
|
|
|
632
|
|
|
|
|
|
|
/* is there a file on disk that matches this exactly? */ |
633
|
0
|
0
|
|
|
|
|
if (!git_fs_path_isfile(path.ptr)) |
634
|
0
|
|
|
|
|
|
continue; |
635
|
|
|
|
|
|
|
|
636
|
|
|
|
|
|
|
/* is that file ignored? */ |
637
|
0
|
0
|
|
|
|
|
if ((error = git_ignore_path_is_ignored(&ignored, repo, filename)) < 0) |
638
|
0
|
|
|
|
|
|
break; |
639
|
|
|
|
|
|
|
|
640
|
0
|
0
|
|
|
|
|
if (ignored) { |
641
|
0
|
|
|
|
|
|
git_error_set(GIT_ERROR_INVALID, "pathspec contains ignored file '%s'", |
642
|
|
|
|
|
|
|
filename); |
643
|
0
|
|
|
|
|
|
error = GIT_EINVALIDSPEC; |
644
|
0
|
|
|
|
|
|
break; |
645
|
|
|
|
|
|
|
} |
646
|
|
|
|
|
|
|
} |
647
|
|
|
|
|
|
|
|
648
|
0
|
|
|
|
|
|
git_index_free(idx); |
649
|
0
|
|
|
|
|
|
git_str_dispose(&path); |
650
|
|
|
|
|
|
|
|
651
|
0
|
|
|
|
|
|
return error; |
652
|
|
|
|
|
|
|
} |