ignore.c 15 KB
Newer Older
1 2 3 4 5 6 7
/*
 * Copyright (C) the libgit2 contributors. All rights reserved.
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */

8 9
#include "ignore.h"

10
#include "git2/ignore.h"
11
#include "common.h"
12
#include "attrcache.h"
13
#include "path.h"
14
#include "config.h"
15
#include "wildmatch.h"
16 17 18

#define GIT_IGNORE_INTERNAL		"[internal]exclude"

19 20
#define GIT_IGNORE_DEFAULT_RULES ".\n..\n.git\n"

21
/**
22 23 24
 * A negative ignore pattern can negate a positive one without
 * wildcards if it is a basename only and equals the basename of
 * the positive pattern. Thus
25 26 27 28
 *
 * foo/bar
 * !bar
 *
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
 * would result in foo/bar being unignored again while
 *
 * moo/foo/bar
 * !foo/bar
 *
 * would do nothing. The reverse also holds true: a positive
 * basename pattern can be negated by unignoring the basename in
 * subdirectories. Thus
 *
 * bar
 * !foo/bar
 *
 * would result in foo/bar being unignored again. As with the
 * first case,
 *
 * foo/bar
 * !moo/foo/bar
 *
 * would do nothing, again.
48 49 50
 */
static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *neg)
{
51
	int (*cmp)(const char *, const char *, size_t);
52
	git_attr_fnmatch *longer, *shorter;
53 54
	char *p;

55 56 57
	if ((rule->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0
	    || (neg->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0)
		return false;
58

59 60 61
	if (neg->flags & GIT_ATTR_FNMATCH_ICASE)
		cmp = git__strncasecmp;
	else
62
		cmp = git__strncmp;
63

64 65
	/* If lengths match we need to have an exact match */
	if (rule->length == neg->length) {
66
		return cmp(rule->pattern, neg->pattern, rule->length) == 0;
67 68 69 70 71 72 73
	} else if (rule->length < neg->length) {
		shorter = rule;
		longer = neg;
	} else {
		shorter = neg;
		longer = rule;
	}
74

75 76 77 78 79
	/* Otherwise, we need to check if the shorter
	 * rule is a basename only (that is, it contains
	 * no path separator) and, if so, if it
	 * matches the tail of the longer rule */
	p = longer->pattern + longer->length - shorter->length;
80

81 82 83 84
	if (p[-1] != '/')
		return false;
	if (memchr(shorter->pattern, '/', shorter->length) != NULL)
		return false;
85

86
	return cmp(p, shorter->pattern, shorter->length) == 0;
87 88 89
}

/**
90 91 92 93 94 95 96 97 98 99 100 101 102 103
 * A negative ignore can only unignore a file which is given explicitly before, thus
 *
 *    foo
 *    !foo/bar
 *
 * does not unignore 'foo/bar' as it's not in the list. However
 *
 *    foo/<star>
 *    !foo/bar
 *
 * does unignore 'foo/bar', as it is contained within the 'foo/<star>' rule.
 */
static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match)
{
104
	int error = 0, wildmatch_flags;
105 106 107 108 109
	size_t i;
	git_attr_fnmatch *rule;
	char *path;
	git_buf buf = GIT_BUF_INIT;

110 111
	*out = 0;

112
	wildmatch_flags = WM_PATHNAME;
113
	if (match->flags & GIT_ATTR_FNMATCH_ICASE)
114
		wildmatch_flags |= WM_CASEFOLD;
115

116 117 118 119 120 121 122 123 124 125
	/* path of the file relative to the workdir, so we match the rules in subdirs */
	if (match->containing_dir) {
		git_buf_puts(&buf, match->containing_dir);
	}
	if (git_buf_puts(&buf, match->pattern) < 0)
		return -1;

	path = git_buf_detach(&buf);

	git_vector_foreach(rules, i, rule) {
126 127
		if (!(rule->flags & GIT_ATTR_FNMATCH_HASWILD)) {
			if (does_negate_pattern(rule, match)) {
128
				error = 0;
129 130 131 132 133 134
				*out = 1;
				goto out;
			}
			else
				continue;
		}
135 136

		git_buf_clear(&buf);
137
		if (rule->containing_dir)
138
			git_buf_puts(&buf, rule->containing_dir);
139
		git_buf_puts(&buf, rule->pattern);
140

141
		if (git_buf_oom(&buf))
142 143
			goto out;

144
		if ((error = wildmatch(git_buf_cstr(&buf), path, wildmatch_flags)) < 0) {
145
			git_error_set(GIT_ERROR_INVALID, "error matching pattern");
146 147 148 149
			goto out;
		}

		/* if we found a match, we want to keep this rule */
150
		if (error != WM_NOMATCH) {
151 152 153 154 155 156 157 158 159 160
			*out = 1;
			error = 0;
			goto out;
		}
	}

	error = 0;

out:
	git__free(path);
161
	git_buf_dispose(&buf);
162 163 164
	return error;
}

165
static int parse_ignore_file(
166
	git_repository *repo, git_attr_file *attrs, const char *data)
167
{
168
	int error = 0;
169
	int ignore_case = false;
170 171
	const char *scan = data, *context = NULL;
	git_attr_fnmatch *match = NULL;
172

173
	if (git_repository__cvar(&ignore_case, repo, GIT_CVAR_IGNORECASE) < 0)
174
		git_error_clear();
175

176
	/* if subdir file path, convert context for file paths */
177 178 179 180
	if (attrs->entry &&
		git_path_root(attrs->entry->path) < 0 &&
		!git__suffixcmp(attrs->entry->path, "/" GIT_IGNORE_FILE))
		context = attrs->entry->path;
181

182
	if (git_mutex_lock(&attrs->lock) < 0) {
183
		git_error_set(GIT_ERROR_OS, "failed to lock ignore file");
184 185 186
		return -1;
	}

187
	while (!error && *scan) {
188 189
		int valid_rule = 1;

190 191 192
		if (!match && !(match = git__calloc(1, sizeof(*match)))) {
			error = -1;
			break;
193 194
		}

195
		match->flags =
196
		    GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG;
197

198
		if (!(error = git_attr_fnmatch__parse(
199
			match, &attrs->pool, context, &scan)))
200
		{
201 202 203 204 205
			match->flags |= GIT_ATTR_FNMATCH_IGNORE;

			if (ignore_case)
				match->flags |= GIT_ATTR_FNMATCH_ICASE;

206
			scan = git__next_line(scan);
207

208 209 210 211 212 213 214 215
			/*
			 * If a negative match doesn't actually do anything,
			 * throw it away. As we cannot always verify whether a
			 * rule containing wildcards negates another rule, we
			 * do not optimize away these rules, though.
			 * */
			if (match->flags & GIT_ATTR_FNMATCH_NEGATIVE
			    && !(match->flags & GIT_ATTR_FNMATCH_HASWILD))
216 217 218 219
				error = does_negate_rule(&valid_rule, &attrs->rules, match);

			if (!error && valid_rule)
				error = git_vector_insert(&attrs->rules, match);
220 221
		}

222
		if (error != 0 || !valid_rule) {
223 224 225
			match->pattern = NULL;

			if (error == GIT_ENOTFOUND)
226
				error = 0;
227 228 229 230 231
		} else {
			match = NULL; /* vector now "owns" the match */
		}
	}

232
	git_mutex_unlock(&attrs->lock);
233
	git__free(match);
234 235 236 237

	return error;
}

238 239 240 241 242 243 244 245 246
static int push_ignore_file(
	git_ignores *ignores,
	git_vector *which_list,
	const char *base,
	const char *filename)
{
	int error = 0;
	git_attr_file *file = NULL;

247
	error = git_attr_cache__get(
248
		&file, ignores->repo, NULL, GIT_ATTR_FILE__FROM_FILE,
249
		base, filename, parse_ignore_file);
250 251 252 253 254 255 256
	if (error < 0)
		return error;

	if (file != NULL) {
		if ((error = git_vector_insert(which_list, file)) < 0)
			git_attr_file__free(file);
	}
257 258 259

	return error;
}
260

261
static int push_one_ignore(void *payload, const char *path)
262
{
263
	git_ignores *ign = payload;
264
	ign->depth++;
265
	return push_ignore_file(ign, &ign->ign_path, path, GIT_IGNORE_FILE);
266 267
}

268
static int get_internal_ignores(git_attr_file **out, git_repository *repo)
269 270 271
{
	int error;

272 273 274 275
	if ((error = git_attr_cache__init(repo)) < 0)
		return error;

	error = git_attr_cache__get(
276
		out, repo, NULL, GIT_ATTR_FILE__IN_MEMORY, NULL, GIT_IGNORE_INTERNAL, NULL);
277

278 279
	/* if internal rules list is empty, insert default rules */
	if (!error && !(*out)->rules.length)
280
		error = parse_ignore_file(repo, *out, GIT_IGNORE_DEFAULT_RULES);
281 282 283 284

	return error;
}

285 286 287 288
int git_ignore__for_path(
	git_repository *repo,
	const char *path,
	git_ignores *ignores)
289
{
290
	int error = 0;
291
	const char *workdir = git_repository_workdir(repo);
292
	git_buf infopath = GIT_BUF_INIT;
293

294
	assert(repo && ignores && path);
295

296
	memset(ignores, 0, sizeof(*ignores));
297 298
	ignores->repo = repo;

299 300 301
	/* Read the ignore_case flag */
	if ((error = git_repository__cvar(
			&ignores->ignore_case, repo, GIT_CVAR_IGNORECASE)) < 0)
302 303
		goto cleanup;

304
	if ((error = git_attr_cache__init(repo)) < 0)
305 306
		goto cleanup;

307
	/* given a unrooted path in a non-bare repo, resolve it */
308 309 310 311 312 313 314 315
	if (workdir && git_path_root(path) < 0) {
		git_buf local = GIT_BUF_INIT;

		if ((error = git_path_dirname_r(&local, path)) < 0 ||
		    (error = git_path_resolve_relative(&local, 0)) < 0 ||
		    (error = git_path_to_dir(&local)) < 0 ||
		    (error = git_buf_joinpath(&ignores->dir, workdir, local.ptr)) < 0)
		{;} /* Nothing, we just want to stop on the first error */
316
		git_buf_dispose(&local);
317
	} else {
318
		error = git_buf_joinpath(&ignores->dir, path, "");
319
	}
320
	if (error < 0)
321 322
		goto cleanup;

323 324 325
	if (workdir && !git__prefixcmp(ignores->dir.ptr, workdir))
		ignores->dir_root = strlen(workdir);

326
	/* set up internals */
327
	if ((error = get_internal_ignores(&ignores->ign_internal, repo)) < 0)
328 329 330
		goto cleanup;

	/* load .gitignore up the path */
331 332
	if (workdir != NULL) {
		error = git_path_walk_up(
333
			&ignores->dir, workdir, push_one_ignore, ignores);
334 335 336
		if (error < 0)
			goto cleanup;
	}
337

338 339 340 341
	if ((error = git_repository_item_path(&infopath,
			repo, GIT_REPOSITORY_ITEM_INFO)) < 0)
		goto cleanup;

342
	/* load .git/info/exclude */
343 344
	error = push_ignore_file(
		ignores, &ignores->ign_global,
345
		infopath.ptr, GIT_IGNORE_FILE_INREPO);
346
	if (error < 0)
347 348 349
		goto cleanup;

	/* load core.excludesfile */
350
	if (git_repository_attr_cache(repo)->cfg_excl_file != NULL)
351 352
		error = push_ignore_file(
			ignores, &ignores->ign_global, NULL,
353
			git_repository_attr_cache(repo)->cfg_excl_file);
354 355

cleanup:
356
	git_buf_dispose(&infopath);
357
	if (error < 0)
358
		git_ignore__free(ignores);
359

360 361 362 363 364
	return error;
}

int git_ignore__push_dir(git_ignores *ign, const char *dir)
{
365 366
	if (git_buf_joinpath(&ign->dir, ign->dir.ptr, dir) < 0)
		return -1;
367

368 369
	ign->depth++;

370
	return push_ignore_file(
371
		ign, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE);
372 373
}

374 375 376 377
int git_ignore__pop_dir(git_ignores *ign)
{
	if (ign->ign_path.length > 0) {
		git_attr_file *file = git_vector_last(&ign->ign_path);
378
		const char *start = file->entry->path, *end;
379

380 381
		/* - ign->dir looks something like "/home/user/a/b/" (or "a/b/c/d/")
		 * - file->path looks something like "a/b/.gitignore
382
		 *
383 384 385
		 * We are popping the last directory off ign->dir.  We also want
		 * to remove the file from the vector if the popped directory
		 * matches the ignore path.  We need to test if the "a/b" part of
386 387 388
		 * the file key matches the path we are about to pop.
		 */

389 390
		if ((end = strrchr(start, '/')) != NULL) {
			size_t dirlen = (end - start) + 1;
391 392
			const char *relpath = ign->dir.ptr + ign->dir_root;
			size_t pathlen = ign->dir.size - ign->dir_root;
393

394
			if (pathlen == dirlen && !memcmp(relpath, start, dirlen)) {
395 396 397
				git_vector_pop(&ign->ign_path);
				git_attr_file__free(file);
			}
398
		}
399
	}
400

401
	if (--ign->depth > 0) {
402
		git_buf_rtruncate_at_char(&ign->dir, '/');
403
		git_path_to_dir(&ign->dir);
404
	}
405

406
	return 0;
407 408
}

409
void git_ignore__free(git_ignores *ignores)
410
{
411 412 413
	unsigned int i;
	git_attr_file *file;

414
	git_attr_file__free(ignores->ign_internal);
415 416 417 418 419

	git_vector_foreach(&ignores->ign_path, i, file) {
		git_attr_file__free(file);
		ignores->ign_path.contents[i] = NULL;
	}
420
	git_vector_free(&ignores->ign_path);
421 422 423 424 425

	git_vector_foreach(&ignores->ign_global, i, file) {
		git_attr_file__free(file);
		ignores->ign_global.contents[i] = NULL;
	}
426
	git_vector_free(&ignores->ign_global);
427

428
	git_buf_dispose(&ignores->dir);
429 430
}

431
static bool ignore_lookup_in_rules(
432
	int *ignored, git_attr_file *file, git_attr_path *path)
433
{
434
	size_t j;
435 436
	git_attr_fnmatch *match;

437
	git_vector_rforeach(&file->rules, j, match) {
438 439 440
		if (match->flags & GIT_ATTR_FNMATCH_DIRECTORY &&
		    path->is_dir == GIT_DIR_FLAG_FALSE)
			continue;
441
		if (git_attr_fnmatch__match(match, path)) {
442 443
			*ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0) ?
				GIT_IGNORE_TRUE : GIT_IGNORE_FALSE;
444
			return true;
445 446 447
		}
	}

448
	return false;
449 450
}

451
int git_ignore__lookup(
452
	int *out, git_ignores *ignores, const char *pathname, git_dir_flag dir_flag)
453
{
454
	unsigned int i;
455 456 457
	git_attr_file *file;
	git_attr_path path;

458 459
	*out = GIT_IGNORE_NOTFOUND;

460
	if (git_attr_path__init(
461
		&path, pathname, git_repository_workdir(ignores->repo), dir_flag) < 0)
462
		return -1;
463

464
	/* first process builtins - success means path was found */
465
	if (ignore_lookup_in_rules(out, ignores->ign_internal, &path))
466
		goto cleanup;
467

468 469
	/* next process files in the path */
	git_vector_foreach(&ignores->ign_path, i, file) {
470
		if (ignore_lookup_in_rules(out, file, &path))
471
			goto cleanup;
472 473
	}

474 475
	/* last process global ignores */
	git_vector_foreach(&ignores->ign_global, i, file) {
476
		if (ignore_lookup_in_rules(out, file, &path))
477
			goto cleanup;
478 479
	}

480 481
cleanup:
	git_attr_path__free(&path);
482
	return 0;
483
}
484

485
int git_ignore_add_rule(git_repository *repo, const char *rules)
486 487
{
	int error;
488
	git_attr_file *ign_internal = NULL;
489

490 491 492 493 494
	if ((error = get_internal_ignores(&ign_internal, repo)) < 0)
		return error;

	error = parse_ignore_file(repo, ign_internal, rules);
	git_attr_file__free(ign_internal);
495 496 497 498

	return error;
}

499
int git_ignore_clear_internal_rules(git_repository *repo)
500 501 502 503
{
	int error;
	git_attr_file *ign_internal;

504 505
	if ((error = get_internal_ignores(&ign_internal, repo)) < 0)
		return error;
506

507 508 509
	if (!(error = git_attr_file__clear_rules(ign_internal, true)))
		error = parse_ignore_file(
			repo, ign_internal, GIT_IGNORE_DEFAULT_RULES);
510

511
	git_attr_file__free(ign_internal);
512 513
	return error;
}
514 515 516 517

int git_ignore_path_is_ignored(
	int *ignored,
	git_repository *repo,
518
	const char *pathname)
519 520
{
	int error;
521 522
	const char *workdir;
	git_attr_path path;
523
	git_ignores ignores;
524 525
	unsigned int i;
	git_attr_file *file;
526
	git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN;
527

528
	assert(repo && ignored && pathname);
529

530
	workdir = git_repository_workdir(repo);
531

532 533
	memset(&path, 0, sizeof(path));
	memset(&ignores, 0, sizeof(ignores));
534

535 536 537
	if (!git__suffixcmp(pathname, "/"))
		dir_flag = GIT_DIR_FLAG_TRUE;
	else if (git_repository_is_bare(repo))
538 539 540
		dir_flag = GIT_DIR_FLAG_FALSE;

	if ((error = git_attr_path__init(&path, pathname, workdir, dir_flag)) < 0 ||
541 542
		(error = git_ignore__for_path(repo, path.path, &ignores)) < 0)
		goto cleanup;
543

544 545
	while (1) {
		/* first process builtins - success means path was found */
546
		if (ignore_lookup_in_rules(ignored, ignores.ign_internal, &path))
547 548 549 550
			goto cleanup;

		/* next process files in the path */
		git_vector_foreach(&ignores.ign_path, i, file) {
551
			if (ignore_lookup_in_rules(ignored, file, &path))
552 553 554 555 556
				goto cleanup;
		}

		/* last process global ignores */
		git_vector_foreach(&ignores.ign_global, i, file) {
557
			if (ignore_lookup_in_rules(ignored, file, &path))
558 559 560
				goto cleanup;
		}

561 562
		/* move up one directory */
		if (path.basename == path.path)
563
			break;
564 565 566 567 568 569 570 571
		path.basename[-1] = '\0';
		while (path.basename > path.path && *path.basename != '/')
			path.basename--;
		if (path.basename > path.path)
			path.basename++;
		path.is_dir = 1;

		if ((error = git_ignore__pop_dir(&ignores)) < 0)
572
			break;
573 574 575 576 577 578
	}

	*ignored = 0;

cleanup:
	git_attr_path__free(&path);
579 580 581 582
	git_ignore__free(&ignores);
	return error;
}

583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626
int git_ignore__check_pathspec_for_exact_ignores(
	git_repository *repo,
	git_vector *vspec,
	bool no_fnmatch)
{
	int error = 0;
	size_t i;
	git_attr_fnmatch *match;
	int ignored;
	git_buf path = GIT_BUF_INIT;
	const char *wd, *filename;
	git_index *idx;

	if ((error = git_repository__ensure_not_bare(
			repo, "validate pathspec")) < 0 ||
		(error = git_repository_index(&idx, repo)) < 0)
		return error;

	wd = git_repository_workdir(repo);

	git_vector_foreach(vspec, i, match) {
		/* skip wildcard matches (if they are being used) */
		if ((match->flags & GIT_ATTR_FNMATCH_HASWILD) != 0 &&
			!no_fnmatch)
			continue;

		filename = match->pattern;

		/* if file is already in the index, it's fine */
		if (git_index_get_bypath(idx, filename, 0) != NULL)
			continue;

		if ((error = git_buf_joinpath(&path, wd, filename)) < 0)
			break;

		/* is there a file on disk that matches this exactly? */
		if (!git_path_isfile(path.ptr))
			continue;

		/* is that file ignored? */
		if ((error = git_ignore_path_is_ignored(&ignored, repo, filename)) < 0)
			break;

		if (ignored) {
627
			git_error_set(GIT_ERROR_INVALID, "pathspec contains ignored file '%s'",
628 629 630 631 632 633 634
				filename);
			error = GIT_EINVALIDSPEC;
			break;
		}
	}

	git_index_free(idx);
635
	git_buf_dispose(&path);
636 637 638 639

	return error;
}