]> git.proxmox.com Git - libgit2.git/blame - src/ignore.c
New upstream version 1.4.3+dfsg.1
[libgit2.git] / src / ignore.c
CommitLineData
eae0bfdc
PP
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
f004c4a8 10#include "git2/ignore.h"
157cef10 11#include "common.h"
823c0e9c 12#include "attrcache.h"
e579e0f7 13#include "fs_path.h"
ec40b7f9 14#include "config.h"
22a2d3d5 15#include "wildmatch.h"
e579e0f7 16#include "path.h"
df743c7d
RB
17
18#define GIT_IGNORE_INTERNAL "[internal]exclude"
df743c7d 19
02df42dd
RB
20#define GIT_IGNORE_DEFAULT_RULES ".\n..\n.git\n"
21
4f358603 22/**
fcb2c1c8
PS
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
4f358603
PS
26 *
27 * foo/bar
28 * !bar
29 *
fcb2c1c8
PS
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.
4f358603
PS
49 */
50static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *neg)
51{
eae0bfdc 52 int (*cmp)(const char *, const char *, size_t);
fcb2c1c8 53 git_attr_fnmatch *longer, *shorter;
4f358603
PS
54 char *p;
55
eae0bfdc
PP
56 if ((rule->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0
57 || (neg->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0)
58 return false;
59
60 if (neg->flags & GIT_ATTR_FNMATCH_ICASE)
61 cmp = git__strncasecmp;
62 else
63 cmp = git__strncmp;
64
65 /* If lengths match we need to have an exact match */
66 if (rule->length == neg->length) {
67 return cmp(rule->pattern, neg->pattern, rule->length) == 0;
68 } else if (rule->length < neg->length) {
69 shorter = rule;
70 longer = neg;
71 } else {
72 shorter = neg;
73 longer = rule;
74 }
fcb2c1c8 75
eae0bfdc
PP
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 p = longer->pattern + longer->length - shorter->length;
4f358603 81
eae0bfdc
PP
82 if (p[-1] != '/')
83 return false;
84 if (memchr(shorter->pattern, '/', shorter->length) != NULL)
85 return false;
4f358603 86
eae0bfdc 87 return cmp(p, shorter->pattern, shorter->length) == 0;
4f358603
PS
88}
89
e05b2ff1
CMN
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/<star>
99 * !foo/bar
100 *
101 * does unignore 'foo/bar', as it is contained within the 'foo/<star>' rule.
102 */
103static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match)
104{
c25aa7cd 105 int error = 0, wildmatch_flags, effective_flags;
e05b2ff1
CMN
106 size_t i;
107 git_attr_fnmatch *rule;
108 char *path;
e579e0f7 109 git_str buf = GIT_STR_INIT;
e05b2ff1 110
4f358603
PS
111 *out = 0;
112
22a2d3d5 113 wildmatch_flags = WM_PATHNAME;
eae0bfdc 114 if (match->flags & GIT_ATTR_FNMATCH_ICASE)
22a2d3d5 115 wildmatch_flags |= WM_CASEFOLD;
eae0bfdc 116
e05b2ff1
CMN
117 /* path of the file relative to the workdir, so we match the rules in subdirs */
118 if (match->containing_dir) {
e579e0f7 119 git_str_puts(&buf, match->containing_dir);
e05b2ff1 120 }
e579e0f7 121 if (git_str_puts(&buf, match->pattern) < 0)
e05b2ff1
CMN
122 return -1;
123
e579e0f7 124 path = git_str_detach(&buf);
e05b2ff1
CMN
125
126 git_vector_foreach(rules, i, rule) {
4f358603
PS
127 if (!(rule->flags & GIT_ATTR_FNMATCH_HASWILD)) {
128 if (does_negate_pattern(rule, match)) {
2c57114f 129 error = 0;
4f358603
PS
130 *out = 1;
131 goto out;
132 }
133 else
134 continue;
135 }
e05b2ff1 136
e579e0f7 137 git_str_clear(&buf);
ac3d33df 138 if (rule->containing_dir)
e579e0f7
MB
139 git_str_puts(&buf, rule->containing_dir);
140 git_str_puts(&buf, rule->pattern);
657afd35 141
e579e0f7 142 if (git_str_oom(&buf))
e05b2ff1
CMN
143 goto out;
144
c25aa7cd
PP
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 effective_flags = wildmatch_flags;
151 if (!(rule->flags & GIT_ATTR_FNMATCH_FULLPATH))
152 effective_flags &= ~WM_PATHNAME;
153
e05b2ff1 154 /* if we found a match, we want to keep this rule */
e579e0f7 155 if ((wildmatch(git_str_cstr(&buf), path, effective_flags)) == WM_MATCH) {
e05b2ff1
CMN
156 *out = 1;
157 error = 0;
158 goto out;
159 }
160 }
161
e05b2ff1
CMN
162 error = 0;
163
164out:
165 git__free(path);
e579e0f7 166 git_str_dispose(&buf);
e05b2ff1
CMN
167 return error;
168}
169
f917481e 170static int parse_ignore_file(
22a2d3d5 171 git_repository *repo, git_attr_file *attrs, const char *data, bool allow_macros)
df743c7d 172{
b709e951 173 int error = 0;
eac76c23 174 int ignore_case = false;
7d490872
RB
175 const char *scan = data, *context = NULL;
176 git_attr_fnmatch *match = NULL;
ec40b7f9 177
22a2d3d5
UG
178 GIT_UNUSED(allow_macros);
179
180 if (git_repository__configmap_lookup(&ignore_case, repo, GIT_CONFIGMAP_IGNORECASE) < 0)
ac3d33df 181 git_error_clear();
df743c7d 182
7d490872 183 /* if subdir file path, convert context for file paths */
823c0e9c 184 if (attrs->entry &&
e579e0f7 185 git_fs_path_root(attrs->entry->path) < 0 &&
823c0e9c
RB
186 !git__suffixcmp(attrs->entry->path, "/" GIT_IGNORE_FILE))
187 context = attrs->entry->path;
df743c7d 188
e6e8530a 189 if (git_mutex_lock(&attrs->lock) < 0) {
ac3d33df 190 git_error_set(GIT_ERROR_OS, "failed to lock ignore file");
e6e8530a
RB
191 return -1;
192 }
193
0d0fa7c3 194 while (!error && *scan) {
e05b2ff1
CMN
195 int valid_rule = 1;
196
17ef678c
RB
197 if (!match && !(match = git__calloc(1, sizeof(*match)))) {
198 error = -1;
199 break;
df743c7d
RB
200 }
201
ac3d33df 202 match->flags =
22a2d3d5 203 GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG;
9939e602 204
4ba64794 205 if (!(error = git_attr_fnmatch__parse(
7d490872 206 match, &attrs->pool, context, &scan)))
19fa2bc1 207 {
edb456c3
PK
208 match->flags |= GIT_ATTR_FNMATCH_IGNORE;
209
210 if (ignore_case)
211 match->flags |= GIT_ATTR_FNMATCH_ICASE;
212
df743c7d 213 scan = git__next_line(scan);
e05b2ff1 214
eae0bfdc
PP
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 if (match->flags & GIT_ATTR_FNMATCH_NEGATIVE
222 && !(match->flags & GIT_ATTR_FNMATCH_HASWILD))
e05b2ff1
CMN
223 error = does_negate_rule(&valid_rule, &attrs->rules, match);
224
225 if (!error && valid_rule)
226 error = git_vector_insert(&attrs->rules, match);
df743c7d
RB
227 }
228
e05b2ff1 229 if (error != 0 || !valid_rule) {
df743c7d
RB
230 match->pattern = NULL;
231
232 if (error == GIT_ENOTFOUND)
0d0fa7c3 233 error = 0;
df743c7d
RB
234 } else {
235 match = NULL; /* vector now "owns" the match */
236 }
237 }
238
e6e8530a 239 git_mutex_unlock(&attrs->lock);
1dbcc9fc 240 git__free(match);
df743c7d 241
df743c7d
RB
242 return error;
243}
244
7d490872
RB
245static int push_ignore_file(
246 git_ignores *ignores,
247 git_vector *which_list,
248 const char *base,
249 const char *filename)
250{
c25aa7cd 251 git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_FILE, base, filename };
7d490872 252 git_attr_file *file = NULL;
c25aa7cd
PP
253 int error = 0;
254
255 error = git_attr_cache__get(&file, ignores->repo, NULL, &source, parse_ignore_file, false);
7d490872 256
2e9d813b
RB
257 if (error < 0)
258 return error;
259
260 if (file != NULL) {
261 if ((error = git_vector_insert(which_list, file)) < 0)
262 git_attr_file__free(file);
263 }
7d490872
RB
264
265 return error;
266}
df743c7d 267
bbb988a5 268static int push_one_ignore(void *payload, const char *path)
0cfcff5d 269{
25e0b157 270 git_ignores *ign = payload;
8f7bc646 271 ign->depth++;
bbb988a5 272 return push_ignore_file(ign, &ign->ign_path, path, GIT_IGNORE_FILE);
0cfcff5d
RB
273}
274
7d490872 275static int get_internal_ignores(git_attr_file **out, git_repository *repo)
02df42dd 276{
c25aa7cd 277 git_attr_file_source source = { GIT_ATTR_FILE_SOURCE_MEMORY, NULL, GIT_IGNORE_INTERNAL };
02df42dd
RB
278 int error;
279
7d490872
RB
280 if ((error = git_attr_cache__init(repo)) < 0)
281 return error;
282
c25aa7cd 283 error = git_attr_cache__get(out, repo, NULL, &source, NULL, false);
02df42dd 284
7d490872
RB
285 /* if internal rules list is empty, insert default rules */
286 if (!error && !(*out)->rules.length)
22a2d3d5 287 error = parse_ignore_file(repo, *out, GIT_IGNORE_DEFAULT_RULES, false);
02df42dd
RB
288
289 return error;
290}
291
f917481e
RB
292int git_ignore__for_path(
293 git_repository *repo,
294 const char *path,
295 git_ignores *ignores)
df743c7d 296{
0d0fa7c3 297 int error = 0;
df743c7d 298 const char *workdir = git_repository_workdir(repo);
e579e0f7 299 git_str infopath = GIT_STR_INIT;
adc9bdb3 300
c25aa7cd
PP
301 GIT_ASSERT_ARG(repo);
302 GIT_ASSERT_ARG(ignores);
303 GIT_ASSERT_ARG(path);
df743c7d 304
2e9d813b 305 memset(ignores, 0, sizeof(*ignores));
b6c93aef 306 ignores->repo = repo;
b6c93aef 307
eac76c23 308 /* Read the ignore_case flag */
22a2d3d5
UG
309 if ((error = git_repository__configmap_lookup(
310 &ignores->ignore_case, repo, GIT_CONFIGMAP_IGNORECASE)) < 0)
ec40b7f9
PK
311 goto cleanup;
312
2e9d813b 313 if ((error = git_attr_cache__init(repo)) < 0)
df743c7d
RB
314 goto cleanup;
315
f917481e 316 /* given a unrooted path in a non-bare repo, resolve it */
e579e0f7
MB
317 if (workdir && git_fs_path_root(path) < 0) {
318 git_str local = GIT_STR_INIT;
319
320 if ((error = git_fs_path_dirname_r(&local, path)) < 0 ||
321 (error = git_fs_path_resolve_relative(&local, 0)) < 0 ||
322 (error = git_fs_path_to_dir(&local)) < 0 ||
323 (error = git_str_joinpath(&ignores->dir, workdir, local.ptr)) < 0 ||
324 (error = git_path_validate_str_length(repo, &ignores->dir)) < 0) {
c25aa7cd
PP
325 /* Nothing, we just want to stop on the first error */
326 }
327
e579e0f7 328 git_str_dispose(&local);
d364dc8b 329 } else {
e579e0f7
MB
330 if (!(error = git_str_joinpath(&ignores->dir, path, "")))
331 error = git_path_validate_str_length(NULL, &ignores->dir);
d364dc8b 332 }
c25aa7cd 333
f917481e 334 if (error < 0)
df743c7d
RB
335 goto cleanup;
336
6a0956e5
RB
337 if (workdir && !git__prefixcmp(ignores->dir.ptr, workdir))
338 ignores->dir_root = strlen(workdir);
339
b6c93aef 340 /* set up internals */
7d490872 341 if ((error = get_internal_ignores(&ignores->ign_internal, repo)) < 0)
df743c7d
RB
342 goto cleanup;
343
344 /* load .gitignore up the path */
f917481e 345 if (workdir != NULL) {
e579e0f7 346 error = git_fs_path_walk_up(
25e0b157 347 &ignores->dir, workdir, push_one_ignore, ignores);
f917481e
RB
348 if (error < 0)
349 goto cleanup;
350 }
df743c7d 351
22a2d3d5 352 /* load .git/info/exclude if possible */
e579e0f7 353 if ((error = git_repository__item_path(&infopath, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 ||
22a2d3d5
UG
354 (error = push_ignore_file(ignores, &ignores->ign_global, infopath.ptr, GIT_IGNORE_FILE_INREPO)) < 0) {
355 if (error != GIT_ENOTFOUND)
356 goto cleanup;
357 error = 0;
358 }
df743c7d
RB
359
360 /* load core.excludesfile */
95dfb031 361 if (git_repository_attr_cache(repo)->cfg_excl_file != NULL)
7d490872
RB
362 error = push_ignore_file(
363 ignores, &ignores->ign_global, NULL,
95dfb031 364 git_repository_attr_cache(repo)->cfg_excl_file);
df743c7d
RB
365
366cleanup:
e579e0f7 367 git_str_dispose(&infopath);
0d0fa7c3 368 if (error < 0)
b6c93aef 369 git_ignore__free(ignores);
95dfb031 370
b6c93aef
RB
371 return error;
372}
373
374int git_ignore__push_dir(git_ignores *ign, const char *dir)
375{
e579e0f7 376 if (git_str_joinpath(&ign->dir, ign->dir.ptr, dir) < 0)
0d0fa7c3 377 return -1;
ba8b8c04 378
8f7bc646
RB
379 ign->depth++;
380
ba8b8c04 381 return push_ignore_file(
7d490872 382 ign, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE);
df743c7d
RB
383}
384
b6c93aef
RB
385int git_ignore__pop_dir(git_ignores *ign)
386{
387 if (ign->ign_path.length > 0) {
388 git_attr_file *file = git_vector_last(&ign->ign_path);
823c0e9c 389 const char *start = file->entry->path, *end;
3bc3ed80 390
7d490872
RB
391 /* - ign->dir looks something like "/home/user/a/b/" (or "a/b/c/d/")
392 * - file->path looks something like "a/b/.gitignore
3bc3ed80 393 *
7d490872
RB
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
3bc3ed80
RB
397 * the file key matches the path we are about to pop.
398 */
399
7d490872
RB
400 if ((end = strrchr(start, '/')) != NULL) {
401 size_t dirlen = (end - start) + 1;
6a0956e5
RB
402 const char *relpath = ign->dir.ptr + ign->dir_root;
403 size_t pathlen = ign->dir.size - ign->dir_root;
3bc3ed80 404
6a0956e5 405 if (pathlen == dirlen && !memcmp(relpath, start, dirlen)) {
7d490872
RB
406 git_vector_pop(&ign->ign_path);
407 git_attr_file__free(file);
408 }
cef170ab 409 }
8f7bc646 410 }
ba8b8c04 411
8f7bc646 412 if (--ign->depth > 0) {
e579e0f7
MB
413 git_str_rtruncate_at_char(&ign->dir, '/');
414 git_fs_path_to_dir(&ign->dir);
b6c93aef 415 }
8f7bc646 416
0d0fa7c3 417 return 0;
b6c93aef
RB
418}
419
adc9bdb3 420void git_ignore__free(git_ignores *ignores)
df743c7d 421{
b8777615
RB
422 unsigned int i;
423 git_attr_file *file;
424
7d490872 425 git_attr_file__free(ignores->ign_internal);
b8777615
RB
426
427 git_vector_foreach(&ignores->ign_path, i, file) {
428 git_attr_file__free(file);
429 ignores->ign_path.contents[i] = NULL;
430 }
b6c93aef 431 git_vector_free(&ignores->ign_path);
b8777615
RB
432
433 git_vector_foreach(&ignores->ign_global, i, file) {
434 git_attr_file__free(file);
435 ignores->ign_global.contents[i] = NULL;
436 }
b6c93aef 437 git_vector_free(&ignores->ign_global);
b8777615 438
e579e0f7 439 git_str_dispose(&ignores->dir);
b6c93aef
RB
440}
441
0d0fa7c3 442static bool ignore_lookup_in_rules(
f554611a 443 int *ignored, git_attr_file *file, git_attr_path *path)
b6c93aef 444{
b8457baa 445 size_t j;
b6c93aef
RB
446 git_attr_fnmatch *match;
447
e6e8530a 448 git_vector_rforeach(&file->rules, j, match) {
ac3d33df
JK
449 if (match->flags & GIT_ATTR_FNMATCH_DIRECTORY &&
450 path->is_dir == GIT_DIR_FLAG_FALSE)
451 continue;
ab43ad2f 452 if (git_attr_fnmatch__match(match, path)) {
f554611a
RB
453 *ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0) ?
454 GIT_IGNORE_TRUE : GIT_IGNORE_FALSE;
0d0fa7c3 455 return true;
b6c93aef
RB
456 }
457 }
458
0d0fa7c3 459 return false;
df743c7d
RB
460}
461
f917481e 462int git_ignore__lookup(
4c09e19a 463 int *out, git_ignores *ignores, const char *pathname, git_dir_flag dir_flag)
df743c7d 464{
22a2d3d5 465 size_t i;
df743c7d
RB
466 git_attr_file *file;
467 git_attr_path path;
df743c7d 468
f554611a
RB
469 *out = GIT_IGNORE_NOTFOUND;
470
0d0fa7c3 471 if (git_attr_path__init(
4c09e19a 472 &path, pathname, git_repository_workdir(ignores->repo), dir_flag) < 0)
0d0fa7c3 473 return -1;
df743c7d 474
0d0fa7c3 475 /* first process builtins - success means path was found */
f554611a 476 if (ignore_lookup_in_rules(out, ignores->ign_internal, &path))
d58336dd 477 goto cleanup;
df743c7d 478
22a2d3d5
UG
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 git_vector_rforeach(&ignores->ign_path, i, file) {
f554611a 484 if (ignore_lookup_in_rules(out, file, &path))
d58336dd 485 goto cleanup;
df743c7d 486 }
df743c7d 487
b6c93aef
RB
488 /* last process global ignores */
489 git_vector_foreach(&ignores->ign_global, i, file) {
f554611a 490 if (ignore_lookup_in_rules(out, file, &path))
d58336dd 491 goto cleanup;
b6c93aef
RB
492 }
493
d58336dd
RB
494cleanup:
495 git_attr_path__free(&path);
0d0fa7c3 496 return 0;
df743c7d 497}
f004c4a8 498
823c0e9c 499int git_ignore_add_rule(git_repository *repo, const char *rules)
f004c4a8
RB
500{
501 int error;
7d490872 502 git_attr_file *ign_internal = NULL;
f004c4a8 503
823c0e9c
RB
504 if ((error = get_internal_ignores(&ign_internal, repo)) < 0)
505 return error;
506
22a2d3d5 507 error = parse_ignore_file(repo, ign_internal, rules, false);
823c0e9c 508 git_attr_file__free(ign_internal);
f004c4a8
RB
509
510 return error;
511}
512
823c0e9c 513int git_ignore_clear_internal_rules(git_repository *repo)
f004c4a8
RB
514{
515 int error;
516 git_attr_file *ign_internal;
517
823c0e9c
RB
518 if ((error = get_internal_ignores(&ign_internal, repo)) < 0)
519 return error;
7d490872 520
823c0e9c
RB
521 if (!(error = git_attr_file__clear_rules(ign_internal, true)))
522 error = parse_ignore_file(
22a2d3d5 523 repo, ign_internal, GIT_IGNORE_DEFAULT_RULES, false);
02df42dd 524
823c0e9c 525 git_attr_file__free(ign_internal);
f004c4a8
RB
526 return error;
527}
2fb4e9b3
RB
528
529int git_ignore_path_is_ignored(
530 int *ignored,
531 git_repository *repo,
52032ae5 532 const char *pathname)
2fb4e9b3
RB
533{
534 int error;
52032ae5
RB
535 const char *workdir;
536 git_attr_path path;
2fb4e9b3 537 git_ignores ignores;
52032ae5
RB
538 unsigned int i;
539 git_attr_file *file;
eae0bfdc 540 git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN;
2fb4e9b3 541
c25aa7cd
PP
542 GIT_ASSERT_ARG(repo);
543 GIT_ASSERT_ARG(ignored);
544 GIT_ASSERT_ARG(pathname);
52032ae5 545
8a349bf2 546 workdir = git_repository_workdir(repo);
52032ae5 547
f554611a
RB
548 memset(&path, 0, sizeof(path));
549 memset(&ignores, 0, sizeof(ignores));
52032ae5 550
6147f643
PP
551 if (!git__suffixcmp(pathname, "/"))
552 dir_flag = GIT_DIR_FLAG_TRUE;
553 else if (git_repository_is_bare(repo))
eae0bfdc
PP
554 dir_flag = GIT_DIR_FLAG_FALSE;
555
556 if ((error = git_attr_path__init(&path, pathname, workdir, dir_flag)) < 0 ||
f554611a
RB
557 (error = git_ignore__for_path(repo, path.path, &ignores)) < 0)
558 goto cleanup;
2fb4e9b3 559
52032ae5 560 while (1) {
52032ae5 561 /* first process builtins - success means path was found */
f554611a 562 if (ignore_lookup_in_rules(ignored, ignores.ign_internal, &path))
52032ae5
RB
563 goto cleanup;
564
565 /* next process files in the path */
566 git_vector_foreach(&ignores.ign_path, i, file) {
f554611a 567 if (ignore_lookup_in_rules(ignored, file, &path))
52032ae5
RB
568 goto cleanup;
569 }
570
571 /* last process global ignores */
572 git_vector_foreach(&ignores.ign_global, i, file) {
f554611a 573 if (ignore_lookup_in_rules(ignored, file, &path))
52032ae5
RB
574 goto cleanup;
575 }
576
f554611a
RB
577 /* move up one directory */
578 if (path.basename == path.path)
52032ae5 579 break;
f554611a
RB
580 path.basename[-1] = '\0';
581 while (path.basename > path.path && *path.basename != '/')
582 path.basename--;
583 if (path.basename > path.path)
584 path.basename++;
585 path.is_dir = 1;
586
587 if ((error = git_ignore__pop_dir(&ignores)) < 0)
ba8b8c04 588 break;
52032ae5
RB
589 }
590
591 *ignored = 0;
592
593cleanup:
594 git_attr_path__free(&path);
2fb4e9b3
RB
595 git_ignore__free(&ignores);
596 return error;
597}
598
85b8b18b
RB
599int git_ignore__check_pathspec_for_exact_ignores(
600 git_repository *repo,
601 git_vector *vspec,
602 bool no_fnmatch)
603{
604 int error = 0;
605 size_t i;
606 git_attr_fnmatch *match;
607 int ignored;
e579e0f7 608 git_str path = GIT_STR_INIT;
c25aa7cd 609 const char *filename;
85b8b18b
RB
610 git_index *idx;
611
612 if ((error = git_repository__ensure_not_bare(
613 repo, "validate pathspec")) < 0 ||
614 (error = git_repository_index(&idx, repo)) < 0)
615 return error;
616
85b8b18b
RB
617 git_vector_foreach(vspec, i, match) {
618 /* skip wildcard matches (if they are being used) */
619 if ((match->flags & GIT_ATTR_FNMATCH_HASWILD) != 0 &&
620 !no_fnmatch)
621 continue;
622
623 filename = match->pattern;
624
625 /* if file is already in the index, it's fine */
626 if (git_index_get_bypath(idx, filename, 0) != NULL)
627 continue;
628
c25aa7cd 629 if ((error = git_repository_workdir_path(&path, repo, filename)) < 0)
85b8b18b
RB
630 break;
631
632 /* is there a file on disk that matches this exactly? */
e579e0f7 633 if (!git_fs_path_isfile(path.ptr))
85b8b18b
RB
634 continue;
635
636 /* is that file ignored? */
637 if ((error = git_ignore_path_is_ignored(&ignored, repo, filename)) < 0)
638 break;
639
640 if (ignored) {
ac3d33df 641 git_error_set(GIT_ERROR_INVALID, "pathspec contains ignored file '%s'",
85b8b18b
RB
642 filename);
643 error = GIT_EINVALIDSPEC;
644 break;
645 }
646 }
647
648 git_index_free(idx);
e579e0f7 649 git_str_dispose(&path);
85b8b18b
RB
650
651 return error;
652}