]> git.proxmox.com Git - libgit2.git/blame - src/ignore.c
Merge pull request #2274 from libgit2/cmn/ssh-expect-username
[libgit2.git] / src / ignore.c
CommitLineData
f004c4a8 1#include "git2/ignore.h"
157cef10 2#include "common.h"
df743c7d 3#include "ignore.h"
823c0e9c 4#include "attrcache.h"
df743c7d 5#include "path.h"
ec40b7f9 6#include "config.h"
df743c7d
RB
7
8#define GIT_IGNORE_INTERNAL "[internal]exclude"
df743c7d 9
02df42dd
RB
10#define GIT_IGNORE_DEFAULT_RULES ".\n..\n.git\n"
11
f917481e 12static int parse_ignore_file(
823c0e9c 13 git_repository *repo, git_attr_file *attrs, const char *data)
df743c7d 14{
b709e951 15 int error = 0;
eac76c23 16 int ignore_case = false;
7d490872
RB
17 const char *scan = data, *context = NULL;
18 git_attr_fnmatch *match = NULL;
ec40b7f9 19
823c0e9c 20 if (git_repository__cvar(&ignore_case, repo, GIT_CVAR_IGNORECASE) < 0)
7d490872 21 giterr_clear();
df743c7d 22
7d490872 23 /* if subdir file path, convert context for file paths */
823c0e9c
RB
24 if (attrs->entry &&
25 git_path_root(attrs->entry->path) < 0 &&
26 !git__suffixcmp(attrs->entry->path, "/" GIT_IGNORE_FILE))
27 context = attrs->entry->path;
df743c7d 28
e6e8530a 29 if (git_mutex_lock(&attrs->lock) < 0) {
823c0e9c 30 giterr_set(GITERR_OS, "Failed to lock ignore file");
e6e8530a
RB
31 return -1;
32 }
33
0d0fa7c3
RB
34 while (!error && *scan) {
35 if (!match) {
36 match = git__calloc(1, sizeof(*match));
37 GITERR_CHECK_ALLOC(match);
df743c7d
RB
38 }
39
4ba64794 40 match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG;
9939e602 41
4ba64794 42 if (!(error = git_attr_fnmatch__parse(
7d490872 43 match, &attrs->pool, context, &scan)))
19fa2bc1 44 {
edb456c3
PK
45 match->flags |= GIT_ATTR_FNMATCH_IGNORE;
46
47 if (ignore_case)
48 match->flags |= GIT_ATTR_FNMATCH_ICASE;
49
df743c7d 50 scan = git__next_line(scan);
7d490872 51 error = git_vector_insert(&attrs->rules, match);
df743c7d
RB
52 }
53
0d0fa7c3 54 if (error != 0) {
df743c7d
RB
55 git__free(match->pattern);
56 match->pattern = NULL;
57
58 if (error == GIT_ENOTFOUND)
0d0fa7c3 59 error = 0;
df743c7d
RB
60 } else {
61 match = NULL; /* vector now "owns" the match */
62 }
63 }
64
e6e8530a 65 git_mutex_unlock(&attrs->lock);
1dbcc9fc 66 git__free(match);
df743c7d 67
df743c7d
RB
68 return error;
69}
70
7d490872
RB
71static int push_ignore_file(
72 git_ignores *ignores,
73 git_vector *which_list,
74 const char *base,
75 const char *filename)
76{
77 int error = 0;
78 git_attr_file *file = NULL;
79
2e9d813b 80 error = git_attr_cache__get(
823c0e9c
RB
81 &file, ignores->repo, GIT_ATTR_FILE__FROM_FILE,
82 base, filename, parse_ignore_file);
2e9d813b
RB
83 if (error < 0)
84 return error;
85
86 if (file != NULL) {
87 if ((error = git_vector_insert(which_list, file)) < 0)
88 git_attr_file__free(file);
89 }
7d490872
RB
90
91 return error;
92}
df743c7d 93
25e0b157 94static int push_one_ignore(void *payload, git_buf *path)
0cfcff5d 95{
25e0b157 96 git_ignores *ign = payload;
8f7bc646 97 ign->depth++;
7d490872 98 return push_ignore_file(ign, &ign->ign_path, path->ptr, GIT_IGNORE_FILE);
0cfcff5d
RB
99}
100
7d490872 101static int get_internal_ignores(git_attr_file **out, git_repository *repo)
02df42dd
RB
102{
103 int error;
104
7d490872
RB
105 if ((error = git_attr_cache__init(repo)) < 0)
106 return error;
107
7d490872 108 error = git_attr_cache__get(
823c0e9c 109 out, repo, GIT_ATTR_FILE__IN_MEMORY, NULL, GIT_IGNORE_INTERNAL, NULL);
02df42dd 110
7d490872
RB
111 /* if internal rules list is empty, insert default rules */
112 if (!error && !(*out)->rules.length)
823c0e9c 113 error = parse_ignore_file(repo, *out, GIT_IGNORE_DEFAULT_RULES);
02df42dd
RB
114
115 return error;
116}
117
f917481e
RB
118int git_ignore__for_path(
119 git_repository *repo,
120 const char *path,
121 git_ignores *ignores)
df743c7d 122{
0d0fa7c3 123 int error = 0;
df743c7d 124 const char *workdir = git_repository_workdir(repo);
adc9bdb3
RB
125
126 assert(ignores);
df743c7d 127
2e9d813b 128 memset(ignores, 0, sizeof(*ignores));
b6c93aef 129 ignores->repo = repo;
b6c93aef 130
eac76c23
RB
131 /* Read the ignore_case flag */
132 if ((error = git_repository__cvar(
133 &ignores->ignore_case, repo, GIT_CVAR_IGNORECASE)) < 0)
ec40b7f9
PK
134 goto cleanup;
135
2e9d813b 136 if ((error = git_attr_cache__init(repo)) < 0)
df743c7d
RB
137 goto cleanup;
138
f917481e
RB
139 /* given a unrooted path in a non-bare repo, resolve it */
140 if (workdir && git_path_root(path) < 0)
141 error = git_path_find_dir(&ignores->dir, path, workdir);
142 else
143 error = git_buf_sets(&ignores->dir, path);
144 if (error < 0)
df743c7d
RB
145 goto cleanup;
146
b6c93aef 147 /* set up internals */
7d490872 148 if ((error = get_internal_ignores(&ignores->ign_internal, repo)) < 0)
df743c7d
RB
149 goto cleanup;
150
151 /* load .gitignore up the path */
f917481e
RB
152 if (workdir != NULL) {
153 error = git_path_walk_up(
25e0b157 154 &ignores->dir, workdir, push_one_ignore, ignores);
f917481e
RB
155 if (error < 0)
156 goto cleanup;
157 }
df743c7d
RB
158
159 /* load .git/info/exclude */
7d490872
RB
160 error = push_ignore_file(
161 ignores, &ignores->ign_global,
95dfb031 162 git_repository_path(repo), GIT_IGNORE_FILE_INREPO);
0d0fa7c3 163 if (error < 0)
df743c7d
RB
164 goto cleanup;
165
166 /* load core.excludesfile */
95dfb031 167 if (git_repository_attr_cache(repo)->cfg_excl_file != NULL)
7d490872
RB
168 error = push_ignore_file(
169 ignores, &ignores->ign_global, NULL,
95dfb031 170 git_repository_attr_cache(repo)->cfg_excl_file);
df743c7d
RB
171
172cleanup:
0d0fa7c3 173 if (error < 0)
b6c93aef 174 git_ignore__free(ignores);
95dfb031 175
b6c93aef
RB
176 return error;
177}
178
179int git_ignore__push_dir(git_ignores *ign, const char *dir)
180{
0d0fa7c3
RB
181 if (git_buf_joinpath(&ign->dir, ign->dir.ptr, dir) < 0)
182 return -1;
ba8b8c04 183
8f7bc646
RB
184 ign->depth++;
185
ba8b8c04 186 return push_ignore_file(
7d490872 187 ign, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE);
df743c7d
RB
188}
189
b6c93aef
RB
190int git_ignore__pop_dir(git_ignores *ign)
191{
192 if (ign->ign_path.length > 0) {
193 git_attr_file *file = git_vector_last(&ign->ign_path);
823c0e9c 194 const char *start = file->entry->path, *end;
3bc3ed80 195
7d490872
RB
196 /* - ign->dir looks something like "/home/user/a/b/" (or "a/b/c/d/")
197 * - file->path looks something like "a/b/.gitignore
3bc3ed80 198 *
7d490872
RB
199 * We are popping the last directory off ign->dir. We also want
200 * to remove the file from the vector if the popped directory
201 * matches the ignore path. We need to test if the "a/b" part of
3bc3ed80
RB
202 * the file key matches the path we are about to pop.
203 */
204
7d490872
RB
205 if ((end = strrchr(start, '/')) != NULL) {
206 size_t dirlen = (end - start) + 1;
3bc3ed80 207
7d490872
RB
208 if (ign->dir.size >= dirlen &&
209 !memcmp(ign->dir.ptr + ign->dir.size - dirlen, start, dirlen))
210 {
211 git_vector_pop(&ign->ign_path);
212 git_attr_file__free(file);
213 }
cef170ab 214 }
8f7bc646 215 }
ba8b8c04 216
8f7bc646 217 if (--ign->depth > 0) {
b6c93aef 218 git_buf_rtruncate_at_char(&ign->dir, '/');
8f7bc646 219 git_path_to_dir(&ign->dir);
b6c93aef 220 }
8f7bc646 221
0d0fa7c3 222 return 0;
b6c93aef
RB
223}
224
adc9bdb3 225void git_ignore__free(git_ignores *ignores)
df743c7d 226{
b8777615
RB
227 unsigned int i;
228 git_attr_file *file;
229
7d490872 230 git_attr_file__free(ignores->ign_internal);
b8777615
RB
231
232 git_vector_foreach(&ignores->ign_path, i, file) {
233 git_attr_file__free(file);
234 ignores->ign_path.contents[i] = NULL;
235 }
b6c93aef 236 git_vector_free(&ignores->ign_path);
b8777615
RB
237
238 git_vector_foreach(&ignores->ign_global, i, file) {
239 git_attr_file__free(file);
240 ignores->ign_global.contents[i] = NULL;
241 }
b6c93aef 242 git_vector_free(&ignores->ign_global);
b8777615 243
b6c93aef
RB
244 git_buf_free(&ignores->dir);
245}
246
0d0fa7c3 247static bool ignore_lookup_in_rules(
e6e8530a 248 git_attr_file *file, git_attr_path *path, int *ignored)
b6c93aef 249{
b8457baa 250 size_t j;
b6c93aef
RB
251 git_attr_fnmatch *match;
252
e6e8530a 253 git_vector_rforeach(&file->rules, j, match) {
ab43ad2f 254 if (git_attr_fnmatch__match(match, path)) {
b6c93aef 255 *ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0);
0d0fa7c3 256 return true;
b6c93aef
RB
257 }
258 }
259
0d0fa7c3 260 return false;
df743c7d
RB
261}
262
f917481e
RB
263int git_ignore__lookup(
264 git_ignores *ignores, const char *pathname, int *ignored)
df743c7d 265{
b6c93aef 266 unsigned int i;
df743c7d
RB
267 git_attr_file *file;
268 git_attr_path path;
df743c7d 269
0d0fa7c3
RB
270 if (git_attr_path__init(
271 &path, pathname, git_repository_workdir(ignores->repo)) < 0)
272 return -1;
df743c7d 273
0d0fa7c3 274 /* first process builtins - success means path was found */
e6e8530a 275 if (ignore_lookup_in_rules(ignores->ign_internal, &path, ignored))
d58336dd 276 goto cleanup;
df743c7d 277
b6c93aef
RB
278 /* next process files in the path */
279 git_vector_foreach(&ignores->ign_path, i, file) {
e6e8530a 280 if (ignore_lookup_in_rules(file, &path, ignored))
d58336dd 281 goto cleanup;
df743c7d 282 }
df743c7d 283
b6c93aef
RB
284 /* last process global ignores */
285 git_vector_foreach(&ignores->ign_global, i, file) {
e6e8530a 286 if (ignore_lookup_in_rules(file, &path, ignored))
d58336dd 287 goto cleanup;
b6c93aef
RB
288 }
289
290 *ignored = 0;
d58336dd
RB
291
292cleanup:
293 git_attr_path__free(&path);
0d0fa7c3 294 return 0;
df743c7d 295}
f004c4a8 296
823c0e9c 297int git_ignore_add_rule(git_repository *repo, const char *rules)
f004c4a8
RB
298{
299 int error;
7d490872 300 git_attr_file *ign_internal = NULL;
f004c4a8 301
823c0e9c
RB
302 if ((error = get_internal_ignores(&ign_internal, repo)) < 0)
303 return error;
304
305 error = parse_ignore_file(repo, ign_internal, rules);
306 git_attr_file__free(ign_internal);
f004c4a8
RB
307
308 return error;
309}
310
823c0e9c 311int git_ignore_clear_internal_rules(git_repository *repo)
f004c4a8
RB
312{
313 int error;
314 git_attr_file *ign_internal;
315
823c0e9c
RB
316 if ((error = get_internal_ignores(&ign_internal, repo)) < 0)
317 return error;
7d490872 318
823c0e9c
RB
319 if (!(error = git_attr_file__clear_rules(ign_internal, true)))
320 error = parse_ignore_file(
321 repo, ign_internal, GIT_IGNORE_DEFAULT_RULES);
02df42dd 322
823c0e9c 323 git_attr_file__free(ign_internal);
f004c4a8
RB
324 return error;
325}
2fb4e9b3
RB
326
327int git_ignore_path_is_ignored(
328 int *ignored,
329 git_repository *repo,
52032ae5 330 const char *pathname)
2fb4e9b3
RB
331{
332 int error;
52032ae5
RB
333 const char *workdir;
334 git_attr_path path;
335 char *tail, *end;
336 bool full_is_dir;
2fb4e9b3 337 git_ignores ignores;
52032ae5
RB
338 unsigned int i;
339 git_attr_file *file;
2fb4e9b3 340
52032ae5
RB
341 assert(ignored && pathname);
342
343 workdir = repo ? git_repository_workdir(repo) : NULL;
344
345 if ((error = git_attr_path__init(&path, pathname, workdir)) < 0)
346 return error;
347
348 tail = path.path;
349 end = &path.full.ptr[path.full.size];
350 full_is_dir = path.is_dir;
2fb4e9b3 351
52032ae5
RB
352 while (1) {
353 /* advance to next component of path */
354 path.basename = tail;
355
356 while (tail < end && *tail != '/') tail++;
357 *tail = '\0';
358
359 path.full.size = (tail - path.full.ptr);
360 path.is_dir = (tail == end) ? full_is_dir : true;
361
ba8b8c04
RB
362 /* initialize ignores the first time through */
363 if (path.basename == path.path &&
364 (error = git_ignore__for_path(repo, path.path, &ignores)) < 0)
52032ae5
RB
365 break;
366
367 /* first process builtins - success means path was found */
e6e8530a 368 if (ignore_lookup_in_rules(ignores.ign_internal, &path, ignored))
52032ae5
RB
369 goto cleanup;
370
371 /* next process files in the path */
372 git_vector_foreach(&ignores.ign_path, i, file) {
e6e8530a 373 if (ignore_lookup_in_rules(file, &path, ignored))
52032ae5
RB
374 goto cleanup;
375 }
376
377 /* last process global ignores */
378 git_vector_foreach(&ignores.ign_global, i, file) {
e6e8530a 379 if (ignore_lookup_in_rules(file, &path, ignored))
52032ae5
RB
380 goto cleanup;
381 }
382
383 /* if we found no rules before reaching the end, we're done */
384 if (tail == end)
385 break;
386
ba8b8c04
RB
387 /* now add this directory to list of ignores */
388 if ((error = git_ignore__push_dir(&ignores, path.path)) < 0)
389 break;
390
52032ae5
RB
391 /* reinstate divider in path */
392 *tail = '/';
393 while (*tail == '/') tail++;
394 }
395
396 *ignored = 0;
397
398cleanup:
399 git_attr_path__free(&path);
2fb4e9b3
RB
400 git_ignore__free(&ignores);
401 return error;
402}
403
85b8b18b
RB
404
405int git_ignore__check_pathspec_for_exact_ignores(
406 git_repository *repo,
407 git_vector *vspec,
408 bool no_fnmatch)
409{
410 int error = 0;
411 size_t i;
412 git_attr_fnmatch *match;
413 int ignored;
414 git_buf path = GIT_BUF_INIT;
415 const char *wd, *filename;
416 git_index *idx;
417
418 if ((error = git_repository__ensure_not_bare(
419 repo, "validate pathspec")) < 0 ||
420 (error = git_repository_index(&idx, repo)) < 0)
421 return error;
422
423 wd = git_repository_workdir(repo);
424
425 git_vector_foreach(vspec, i, match) {
426 /* skip wildcard matches (if they are being used) */
427 if ((match->flags & GIT_ATTR_FNMATCH_HASWILD) != 0 &&
428 !no_fnmatch)
429 continue;
430
431 filename = match->pattern;
432
433 /* if file is already in the index, it's fine */
434 if (git_index_get_bypath(idx, filename, 0) != NULL)
435 continue;
436
437 if ((error = git_buf_joinpath(&path, wd, filename)) < 0)
438 break;
439
440 /* is there a file on disk that matches this exactly? */
441 if (!git_path_isfile(path.ptr))
442 continue;
443
444 /* is that file ignored? */
445 if ((error = git_ignore_path_is_ignored(&ignored, repo, filename)) < 0)
446 break;
447
448 if (ignored) {
449 giterr_set(GITERR_INVALID, "pathspec contains ignored file '%s'",
450 filename);
451 error = GIT_EINVALIDSPEC;
452 break;
453 }
454 }
455
456 git_index_free(idx);
457 git_buf_free(&path);
458
459 return error;
460}
461