pathspec.c 18 KB
Newer Older
1
#include "cache.h"
2
#include "config.h"
3 4
#include "dir.h"
#include "pathspec.h"
5
#include "attr.h"
6 7
#include "argv-array.h"
#include "quote.h"
8 9 10 11 12 13 14 15 16 17 18 19

/*
 * Finds which of the given pathspecs match items in the index.
 *
 * For each pathspec, sets the corresponding entry in the seen[] array
 * (which should be specs items long, i.e. the same size as pathspec)
 * to the nature of the "closest" (i.e. most specific) match found for
 * that pathspec in the index, if it was a closer type of match than
 * the existing entry.  As an optimization, matching is skipped
 * altogether if seen[] already only contains non-zero entries.
 *
 * If seen[] has not already been written to, it may make sense
20
 * to use find_pathspecs_matching_against_index() instead.
21
 */
22
void add_pathspec_matches_against_index(const struct pathspec *pathspec,
23
					const struct index_state *istate,
24
					char *seen)
25 26 27 28 29 30 31 32 33
{
	int num_unmatched = 0, i;

	/*
	 * Since we are walking the index as if we were walking the directory,
	 * we have to mark the matched pathspec as seen; otherwise we will
	 * mistakenly think that the user gave a pathspec that did not match
	 * anything.
	 */
34
	for (i = 0; i < pathspec->nr; i++)
35 36 37 38
		if (!seen[i])
			num_unmatched++;
	if (!num_unmatched)
		return;
39 40
	for (i = 0; i < istate->cache_nr; i++) {
		const struct cache_entry *ce = istate->cache[i];
41
		ce_path_match(istate, ce, pathspec, seen);
42 43 44 45 46 47
	}
}

/*
 * Finds which of the given pathspecs match items in the index.
 *
48 49 50 51
 * This is a one-shot wrapper around add_pathspec_matches_against_index()
 * which allocates, populates, and returns a seen[] array indicating the
 * nature of the "closest" (i.e. most specific) matches which each of the
 * given pathspecs achieves against all items in the index.
52
 */
53 54
char *find_pathspecs_matching_against_index(const struct pathspec *pathspec,
					    const struct index_state *istate)
55
{
56
	char *seen = xcalloc(pathspec->nr, 1);
57
	add_pathspec_matches_against_index(pathspec, istate, seen);
58 59
	return seen;
}
60 61

/*
62 63 64 65 66 67 68 69 70 71 72 73 74 75
 * Magic pathspec
 *
 * Possible future magic semantics include stuff like:
 *
 *	{ PATHSPEC_RECURSIVE, '*', "recursive" },
 *	{ PATHSPEC_REGEXP, '\0', "regexp" },
 *
 */

static struct pathspec_magic {
	unsigned bit;
	char mnemonic; /* this cannot be ':'! */
	const char *name;
} pathspec_magic[] = {
76 77 78 79 80
	{ PATHSPEC_FROMTOP,  '/', "top" },
	{ PATHSPEC_LITERAL, '\0', "literal" },
	{ PATHSPEC_GLOB,    '\0', "glob" },
	{ PATHSPEC_ICASE,   '\0', "icase" },
	{ PATHSPEC_EXCLUDE,  '!', "exclude" },
81
	{ PATHSPEC_ATTR,    '\0', "attr" },
82 83
};

84
static void prefix_magic(struct strbuf *sb, int prefixlen, unsigned magic)
85 86 87 88
{
	int i;
	strbuf_addstr(sb, ":(");
	for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
89
		if (magic & pathspec_magic[i].bit) {
90 91 92 93 94 95 96
			if (sb->buf[sb->len - 1] != '(')
				strbuf_addch(sb, ',');
			strbuf_addstr(sb, pathspec_magic[i].name);
		}
	strbuf_addf(sb, ",prefix:%d)", prefixlen);
}

97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
static size_t strcspn_escaped(const char *s, const char *stop)
{
	const char *i;

	for (i = s; *i; i++) {
		/* skip the escaped character */
		if (i[0] == '\\' && i[1]) {
			i++;
			continue;
		}

		if (strchr(stop, *i))
			break;
	}
	return i - s;
}

static inline int invalid_value_char(const char ch)
{
	if (isalnum(ch) || strchr(",-_", ch))
		return 0;
	return -1;
}

static char *attr_value_unescape(const char *value)
{
	const char *src;
	char *dst, *ret;

	ret = xmallocz(strlen(value));
	for (src = value, dst = ret; *src; src++, dst++) {
		if (*src == '\\') {
			if (!src[1])
				die(_("Escape character '\\' not allowed as "
				      "last character in attr value"));
			src++;
		}
		if (invalid_value_char(*src))
			die("cannot use '%c' for value matching", *src);
		*dst = *src;
	}
	*dst = '\0';
	return ret;
}

142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
static void parse_pathspec_attr_match(struct pathspec_item *item, const char *value)
{
	struct string_list_item *si;
	struct string_list list = STRING_LIST_INIT_DUP;

	if (item->attr_check || item->attr_match)
		die(_("Only one 'attr:' specification is allowed."));

	if (!value || !*value)
		die(_("attr spec must not be empty"));

	string_list_split(&list, value, ' ', -1);
	string_list_remove_empty_items(&list, 0);

	item->attr_check = attr_check_alloc();
	item->attr_match = xcalloc(list.nr, sizeof(struct attr_match));

	for_each_string_list_item(si, &list) {
		size_t attr_len;
		char *attr_name;
		const struct git_attr *a;

		int j = item->attr_match_nr++;
		const char *attr = si->string;
		struct attr_match *am = &item->attr_match[j];

		switch (*attr) {
		case '!':
			am->match_mode = MATCH_UNSPECIFIED;
			attr++;
			attr_len = strlen(attr);
			break;
		case '-':
			am->match_mode = MATCH_UNSET;
			attr++;
			attr_len = strlen(attr);
			break;
		default:
			attr_len = strcspn(attr, "=");
			if (attr[attr_len] != '=')
				am->match_mode = MATCH_SET;
			else {
184
				const char *v = &attr[attr_len + 1];
185
				am->match_mode = MATCH_VALUE;
186
				am->value = attr_value_unescape(v);
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
			}
			break;
		}

		attr_name = xmemdupz(attr, attr_len);
		a = git_attr(attr_name);
		if (!a)
			die(_("invalid attribute name %s"), attr_name);

		attr_check_append(item->attr_check, a);

		free(attr_name);
	}

	if (item->attr_check->nr != item->attr_match_nr)
202
		BUG("should have same number of entries");
203 204 205 206

	string_list_clear(&list, 0);
}

207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
static inline int get_literal_global(void)
{
	static int literal = -1;

	if (literal < 0)
		literal = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0);

	return literal;
}

static inline int get_glob_global(void)
{
	static int glob = -1;

	if (glob < 0)
		glob = git_env_bool(GIT_GLOB_PATHSPECS_ENVIRONMENT, 0);

	return glob;
}

static inline int get_noglob_global(void)
{
	static int noglob = -1;

	if (noglob < 0)
		noglob = git_env_bool(GIT_NOGLOB_PATHSPECS_ENVIRONMENT, 0);

	return noglob;
}

static inline int get_icase_global(void)
{
	static int icase = -1;

	if (icase < 0)
		icase = git_env_bool(GIT_ICASE_PATHSPECS_ENVIRONMENT, 0);

	return icase;
}

static int get_global_magic(int element_magic)
{
	int global_magic = 0;

	if (get_literal_global())
		global_magic |= PATHSPEC_LITERAL;

	/* --glob-pathspec is overridden by :(literal) */
	if (get_glob_global() && !(element_magic & PATHSPEC_LITERAL))
		global_magic |= PATHSPEC_GLOB;

	if (get_glob_global() && get_noglob_global())
		die(_("global 'glob' and 'noglob' pathspec settings are incompatible"));

	if (get_icase_global())
		global_magic |= PATHSPEC_ICASE;

	if ((global_magic & PATHSPEC_LITERAL) &&
	    (global_magic & ~PATHSPEC_LITERAL))
		die(_("global 'literal' pathspec setting is incompatible "
		      "with all other global pathspec settings"));

	/* --noglob-pathspec adds :(literal) _unless_ :(glob) is specified */
	if (get_noglob_global() && !(element_magic & PATHSPEC_GLOB))
		global_magic |= PATHSPEC_LITERAL;

	return global_magic;
}

276 277 278 279 280 281 282 283
/*
 * Parse the pathspec element looking for long magic
 *
 * saves all magic in 'magic'
 * if prefix magic is used, save the prefix length in 'prefix_len'
 * returns the position in 'elem' after all magic has been parsed
 */
static const char *parse_long_magic(unsigned *magic, int *prefix_len,
284
				    struct pathspec_item *item,
285 286 287 288 289 290
				    const char *elem)
{
	const char *pos;
	const char *nextat;

	for (pos = elem + 2; *pos && *pos != ')'; pos = nextat) {
291
		size_t len = strcspn_escaped(pos, ",)");
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
		int i;

		if (pos[len] == ',')
			nextat = pos + len + 1; /* handle ',' */
		else
			nextat = pos + len; /* handle ')' and '\0' */

		if (!len)
			continue;

		if (starts_with(pos, "prefix:")) {
			char *endptr;
			*prefix_len = strtol(pos + 7, &endptr, 10);
			if (endptr - pos != len)
				die(_("invalid parameter for pathspec magic 'prefix'"));
			continue;
		}

310 311 312 313 314 315 316 317
		if (starts_with(pos, "attr:")) {
			char *attr_body = xmemdupz(pos + 5, len - 5);
			parse_pathspec_attr_match(item, attr_body);
			*magic |= PATHSPEC_ATTR;
			free(attr_body);
			continue;
		}

318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
		for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {
			if (strlen(pathspec_magic[i].name) == len &&
			    !strncmp(pathspec_magic[i].name, pos, len)) {
				*magic |= pathspec_magic[i].bit;
				break;
			}
		}

		if (ARRAY_SIZE(pathspec_magic) <= i)
			die(_("Invalid pathspec magic '%.*s' in '%s'"),
			    (int) len, pos, elem);
	}

	if (*pos != ')')
		die(_("Missing ')' at the end of pathspec magic in '%s'"),
		    elem);
	pos++;

	return pos;
}

339 340 341 342 343 344 345 346 347 348 349 350 351 352
/*
 * Parse the pathspec element looking for short magic
 *
 * saves all magic in 'magic'
 * returns the position in 'elem' after all magic has been parsed
 */
static const char *parse_short_magic(unsigned *magic, const char *elem)
{
	const char *pos;

	for (pos = elem + 1; *pos && *pos != ':'; pos++) {
		char ch = *pos;
		int i;

353 354 355 356 357 358
		/* Special case alias for '!' */
		if (ch == '^') {
			*magic |= PATHSPEC_EXCLUDE;
			continue;
		}

359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
		if (!is_pathspec_magic(ch))
			break;

		for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {
			if (pathspec_magic[i].mnemonic == ch) {
				*magic |= pathspec_magic[i].bit;
				break;
			}
		}

		if (ARRAY_SIZE(pathspec_magic) <= i)
			die(_("Unimplemented pathspec magic '%c' in '%s'"),
			    ch, elem);
	}

	if (*pos == ':')
		pos++;

	return pos;
}

380
static const char *parse_element_magic(unsigned *magic, int *prefix_len,
381
				       struct pathspec_item *item,
382 383 384 385 386 387
				       const char *elem)
{
	if (elem[0] != ':' || get_literal_global())
		return elem; /* nothing to do */
	else if (elem[1] == '(')
		/* longhand */
388
		return parse_long_magic(magic, prefix_len, item, elem);
389 390 391 392 393
	else
		/* shorthand */
		return parse_short_magic(magic, elem);
}

394
/*
395
 * Perform the initialization of a pathspec_item based on a pathspec element.
396
 */
397 398 399
static void init_pathspec_item(struct pathspec_item *item, unsigned flags,
			       const char *prefix, int prefixlen,
			       const char *elt)
400
{
401
	unsigned magic = 0, element_magic = 0;
402
	const char *copyfrom = elt;
403
	char *match;
404
	int pathspec_prefix = -1;
405

406 407 408 409
	item->attr_check = NULL;
	item->attr_match = NULL;
	item->attr_match_nr = 0;

410
	/* PATHSPEC_LITERAL_PATH ignores magic */
411
	if (flags & PATHSPEC_LITERAL_PATH) {
412
		magic = PATHSPEC_LITERAL;
413 414 415
	} else {
		copyfrom = parse_element_magic(&element_magic,
					       &pathspec_prefix,
416
					       item,
417 418
					       elt);
		magic |= element_magic;
419
		magic |= get_global_magic(element_magic);
420
	}
421

422 423
	item->magic = magic;

424 425
	if (pathspec_prefix >= 0 &&
	    (prefixlen || (prefix && *prefix)))
426
		BUG("'prefix' magic is supposed to be used at worktree's root");
427

428 429 430
	if ((magic & PATHSPEC_LITERAL) && (magic & PATHSPEC_GLOB))
		die(_("%s: 'literal' and 'glob' are incompatible"), elt);

431
	/* Create match string which will be used for pathspec matching */
432 433 434 435
	if (pathspec_prefix >= 0) {
		match = xstrdup(copyfrom);
		prefixlen = pathspec_prefix;
	} else if (magic & PATHSPEC_FROMTOP) {
436
		match = xstrdup(copyfrom);
437 438
		prefixlen = 0;
	} else {
439 440
		match = prefix_path_gently(prefix, prefixlen,
					   &prefixlen, copyfrom);
441 442 443
		if (!match)
			die(_("%s: '%s' is outside repository"), elt, copyfrom);
	}
444

445
	item->match = match;
446 447 448
	item->len = strlen(item->match);
	item->prefix = prefixlen;

449 450 451 452
	/*
	 * Prefix the pathspec (keep all magic) and assign to
	 * original. Useful for passing to another command.
	 */
453
	if ((flags & PATHSPEC_PREFIX_ORIGIN) &&
454
	    !get_literal_global()) {
455
		struct strbuf sb = STRBUF_INIT;
456 457 458 459

		/* Preserve the actual prefix length of each pattern */
		prefix_magic(&sb, prefixlen, element_magic);

460 461
		strbuf_addstr(&sb, match);
		item->original = strbuf_detach(&sb, NULL);
462 463 464
	} else {
		item->original = xstrdup(elt);
	}
465

466
	if (magic & PATHSPEC_LITERAL) {
467
		item->nowildcard_len = item->len;
468
	} else {
469
		item->nowildcard_len = simple_length(item->match);
470 471 472
		if (item->nowildcard_len < prefixlen)
			item->nowildcard_len = prefixlen;
	}
473

474
	item->flags = 0;
475 476 477 478 479 480 481 482 483 484
	if (magic & PATHSPEC_GLOB) {
		/*
		 * FIXME: should we enable ONESTAR in _GLOB for
		 * pattern "* * / * . c"?
		 */
	} else {
		if (item->nowildcard_len < item->len &&
		    item->match[item->nowildcard_len] == '*' &&
		    no_wildcard(item->match + item->nowildcard_len + 1))
			item->flags |= PATHSPEC_ONESTAR;
485
	}
486 487

	/* sanity checks, pathspec matchers assume these are sane */
488 489
	if (item->nowildcard_len > item->len ||
	    item->prefix         > item->len) {
490
		BUG("error initializing pathspec_item");
491
	}
492 493 494 495 496 497 498 499 500 501 502 503
}

static int pathspec_item_cmp(const void *a_, const void *b_)
{
	struct pathspec_item *a, *b;

	a = (struct pathspec_item *)a_;
	b = (struct pathspec_item *)b_;
	return strcmp(a->match, b->match);
}

static void NORETURN unsupported_magic(const char *pattern,
504
				       unsigned magic)
505 506
{
	struct strbuf sb = STRBUF_INIT;
507 508
	int i;
	for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {
509 510 511 512
		const struct pathspec_magic *m = pathspec_magic + i;
		if (!(magic & m->bit))
			continue;
		if (sb.len)
513 514 515 516 517
			strbuf_addstr(&sb, ", ");

		if (m->mnemonic)
			strbuf_addf(&sb, _("'%s' (mnemonic: '%c')"),
				    m->name, m->mnemonic);
518 519 520 521 522 523 524 525 526 527
		else
			strbuf_addf(&sb, "'%s'", m->name);
	}
	/*
	 * We may want to substitute "this command" with a command
	 * name. E.g. when add--interactive dies when running
	 * "checkout -p"
	 */
	die(_("%s: pathspec magic not supported by this command: %s"),
	    pattern, sb.buf);
528
}
529

530 531 532
void parse_pathspec(struct pathspec *pathspec,
		    unsigned magic_mask, unsigned flags,
		    const char *prefix, const char **argv)
533
{
534 535
	struct pathspec_item *item;
	const char *entry = argv ? *argv : NULL;
536
	int i, n, prefixlen, nr_exclude = 0;
537 538 539

	memset(pathspec, 0, sizeof(*pathspec));

540 541 542
	if (flags & PATHSPEC_MAXDEPTH_VALID)
		pathspec->magic |= PATHSPEC_MAXDEPTH;

543 544 545 546
	/* No arguments, no prefix -> no pathspec */
	if (!entry && !prefix)
		return;

547 548
	if ((flags & PATHSPEC_PREFER_CWD) &&
	    (flags & PATHSPEC_PREFER_FULL))
549
		BUG("PATHSPEC_PREFER_CWD and PATHSPEC_PREFER_FULL are incompatible");
550

551 552
	/* No arguments with prefix -> prefix pathspec */
	if (!entry) {
553 554 555 556
		if (flags & PATHSPEC_PREFER_FULL)
			return;

		if (!(flags & PATHSPEC_PREFER_CWD))
557
			BUG("PATHSPEC_PREFER_CWD requires arguments");
558

559
		pathspec->items = item = xcalloc(1, sizeof(*item));
560 561
		item->match = xstrdup(prefix);
		item->original = xstrdup(prefix);
562
		item->nowildcard_len = item->len = strlen(prefix);
563
		item->prefix = item->len;
564 565
		pathspec->nr = 1;
		return;
566
	}
567 568

	n = 0;
569
	while (argv[n]) {
570 571 572
		if (*argv[n] == '\0')
			die("empty string is not a valid pathspec. "
				  "please use . instead if you meant to match all paths");
573
		n++;
574
	}
575 576

	pathspec->nr = n;
577
	ALLOC_ARRAY(pathspec->items, n + 1);
578
	item = pathspec->items;
579 580 581 582 583
	prefixlen = prefix ? strlen(prefix) : 0;

	for (i = 0; i < n; i++) {
		entry = argv[i];

584
		init_pathspec_item(item + i, flags, prefix, prefixlen, entry);
585

586 587
		if (item[i].magic & PATHSPEC_EXCLUDE)
			nr_exclude++;
588
		if (item[i].magic & magic_mask)
589
			unsupported_magic(entry, item[i].magic & magic_mask);
590 591 592 593 594 595

		if ((flags & PATHSPEC_SYMLINK_LEADING_PATH) &&
		    has_symlink_leading_path(item[i].match, item[i].len)) {
			die(_("pathspec '%s' is beyond a symbolic link"), entry);
		}

596 597 598 599 600
		if (item[i].nowildcard_len < item[i].len)
			pathspec->has_wildcard = 1;
		pathspec->magic |= item[i].magic;
	}

601 602
	/*
	 * If everything is an exclude pattern, add one positive pattern
Ville Skyttä's avatar
Ville Skyttä committed
603
	 * that matches everything. We allocated an extra one for this.
604 605 606 607 608 609
	 */
	if (nr_exclude == n) {
		int plen = (!(flags & PATHSPEC_PREFER_CWD)) ? 0 : prefixlen;
		init_pathspec_item(item + n, 0, prefix, plen, "");
		pathspec->nr++;
	}
610 611 612

	if (pathspec->magic & PATHSPEC_MAXDEPTH) {
		if (flags & PATHSPEC_KEEP_ORDER)
613
			BUG("PATHSPEC_MAXDEPTH_VALID and PATHSPEC_KEEP_ORDER are incompatible");
René Scharfe's avatar
René Scharfe committed
614
		QSORT(pathspec->items, pathspec->nr, pathspec_item_cmp);
615
	}
616 617
}

618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653
void parse_pathspec_file(struct pathspec *pathspec, unsigned magic_mask,
			 unsigned flags, const char *prefix,
			 const char *file, int nul_term_line)
{
	struct argv_array parsed_file = ARGV_ARRAY_INIT;
	strbuf_getline_fn getline_fn = nul_term_line ? strbuf_getline_nul :
						       strbuf_getline;
	struct strbuf buf = STRBUF_INIT;
	struct strbuf unquoted = STRBUF_INIT;
	FILE *in;

	if (!strcmp(file, "-"))
		in = stdin;
	else
		in = xfopen(file, "r");

	while (getline_fn(&buf, in) != EOF) {
		if (!nul_term_line && buf.buf[0] == '"') {
			strbuf_reset(&unquoted);
			if (unquote_c_style(&unquoted, buf.buf, NULL))
				die(_("line is badly quoted: %s"), buf.buf);
			strbuf_swap(&buf, &unquoted);
		}
		argv_array_push(&parsed_file, buf.buf);
		strbuf_reset(&buf);
	}

	strbuf_release(&unquoted);
	strbuf_release(&buf);
	if (in != stdin)
		fclose(in);

	parse_pathspec(pathspec, magic_mask, flags, prefix, parsed_file.argv);
	argv_array_clear(&parsed_file);
}

Duy Nguyen's avatar
Duy Nguyen committed
654 655
void copy_pathspec(struct pathspec *dst, const struct pathspec *src)
{
656
	int i, j;
657

Duy Nguyen's avatar
Duy Nguyen committed
658
	*dst = *src;
659
	ALLOC_ARRAY(dst->items, dst->nr);
René Scharfe's avatar
René Scharfe committed
660
	COPY_ARRAY(dst->items, src->items, dst->nr);
661 662

	for (i = 0; i < dst->nr; i++) {
663 664 665 666 667 668 669 670 671 672 673 674 675 676
		struct pathspec_item *d = &dst->items[i];
		struct pathspec_item *s = &src->items[i];

		d->match = xstrdup(s->match);
		d->original = xstrdup(s->original);

		ALLOC_ARRAY(d->attr_match, d->attr_match_nr);
		COPY_ARRAY(d->attr_match, s->attr_match, d->attr_match_nr);
		for (j = 0; j < d->attr_match_nr; j++) {
			const char *value = s->attr_match[j].value;
			d->attr_match[j].value = xstrdup_or_null(value);
		}

		d->attr_check = attr_check_dup(s->attr_check);
677
	}
Duy Nguyen's avatar
Duy Nguyen committed
678
}
679

680
void clear_pathspec(struct pathspec *pathspec)
681
{
682
	int i, j;
683 684 685 686

	for (i = 0; i < pathspec->nr; i++) {
		free(pathspec->items[i].match);
		free(pathspec->items[i].original);
687

688
		for (j = 0; j < pathspec->items[i].attr_match_nr; j++)
689 690 691 692 693
			free(pathspec->items[i].attr_match[j].value);
		free(pathspec->items[i].attr_match);

		if (pathspec->items[i].attr_check)
			attr_check_free(pathspec->items[i].attr_check);
694
	}
695

696
	FREE_AND_NULL(pathspec->items);
697
	pathspec->nr = 0;
698
}
699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736

int match_pathspec_attrs(const struct index_state *istate,
			 const char *name, int namelen,
			 const struct pathspec_item *item)
{
	int i;
	char *to_free = NULL;

	if (name[namelen])
		name = to_free = xmemdupz(name, namelen);

	git_check_attr(istate, name, item->attr_check);

	free(to_free);

	for (i = 0; i < item->attr_match_nr; i++) {
		const char *value;
		int matched;
		enum attr_match_mode match_mode;

		value = item->attr_check->items[i].value;
		match_mode = item->attr_match[i].match_mode;

		if (ATTR_TRUE(value))
			matched = (match_mode == MATCH_SET);
		else if (ATTR_FALSE(value))
			matched = (match_mode == MATCH_UNSET);
		else if (ATTR_UNSET(value))
			matched = (match_mode == MATCH_UNSPECIFIED);
		else
			matched = (match_mode == MATCH_VALUE &&
				   !strcmp(item->attr_match[i].value, value));
		if (!matched)
			return 0;
	}

	return 1;
}