attr_file.c 21.2 KB
Newer Older
1
#include "common.h"
2
#include "repository.h"
3
#include "filebuf.h"
4
#include "attr_file.h"
5
#include "attrcache.h"
6 7
#include "git2/blob.h"
#include "git2/tree.h"
8
#include "index.h"
9 10
#include <ctype.h>

11 12
static void attr_file_free(git_attr_file *file)
{
13 14
	bool unlock = !git_mutex_lock(&file->lock);
	git_attr_file__clear_rules(file, false);
15
	git_pool_clear(&file->pool);
16 17 18 19
	if (unlock)
		git_mutex_unlock(&file->lock);
	git_mutex_free(&file->lock);

20 21 22
	git__memzero(file, sizeof(*file));
	git__free(file);
}
23

24
int git_attr_file__new(
25
	git_attr_file **out,
26 27
	git_attr_file_entry *entry,
	git_attr_file_source source)
28
{
29
	git_attr_file *attrs = git__calloc(1, sizeof(git_attr_file));
30
	GITERR_CHECK_ALLOC(attrs);
31

32 33 34 35 36 37
	if (git_mutex_init(&attrs->lock) < 0) {
		giterr_set(GITERR_OS, "Failed to initialize lock");
		git__free(attrs);
		return -1;
	}

38
	if (git_pool_init(&attrs->pool, 1, 0) < 0) {
39 40
		attr_file_free(attrs);
		return -1;
41 42
	}

43
	GIT_REFCOUNT_INC(attrs);
44
	attrs->entry  = entry;
45 46 47 48
	attrs->source = source;
	*out = attrs;
	return 0;
}
49

50
int git_attr_file__clear_rules(git_attr_file *file, bool need_lock)
51 52 53
{
	unsigned int i;
	git_attr_rule *rule;
54

55 56 57 58 59
	if (need_lock && git_mutex_lock(&file->lock) < 0) {
		giterr_set(GITERR_OS, "Failed to lock attribute file");
		return -1;
	}

60 61 62
	git_vector_foreach(&file->rules, i, rule)
		git_attr_rule__free(rule);
	git_vector_free(&file->rules);
63 64 65 66 67

	if (need_lock)
		git_mutex_unlock(&file->lock);

	return 0;
68
}
69

70 71 72 73 74 75
void git_attr_file__free(git_attr_file *file)
{
	if (!file)
		return;
	GIT_REFCOUNT_DEC(file, attr_file_free);
}
76

77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
static int attr_file_oid_from_index(
	git_oid *oid, git_repository *repo, const char *path)
{
	int error;
	git_index *idx;
	size_t pos;
	const git_index_entry *entry;

	if ((error = git_repository_index__weakptr(&idx, repo)) < 0 ||
		(error = git_index__find_pos(&pos, idx, path, 0, 0)) < 0)
		return error;

	if (!(entry = git_index_get_byindex(idx, pos)))
		return GIT_ENOTFOUND;

	*oid = entry->id;
93
	return 0;
94 95 96 97 98
}

int git_attr_file__load(
	git_attr_file **out,
	git_repository *repo,
99
	git_attr_session *attr_session,
100 101 102
	git_attr_file_entry *entry,
	git_attr_file_source source,
	git_attr_file_parser parser)
103 104 105 106 107
{
	int error = 0;
	git_blob *blob = NULL;
	git_buf content = GIT_BUF_INIT;
	git_attr_file *file;
108
	struct stat st;
109
	bool nonexistent = false;
110 111 112

	*out = NULL;

113 114 115 116 117
	switch (source) {
	case GIT_ATTR_FILE__IN_MEMORY:
		/* in-memory attribute file doesn't need data */
		break;
	case GIT_ATTR_FILE__FROM_INDEX: {
118
		git_oid id;
119

120
		if ((error = attr_file_oid_from_index(&id, repo, entry->path)) < 0 ||
121 122 123
			(error = git_blob_lookup(&blob, repo, &id)) < 0)
			return error;

124 125 126
		/* Do not assume that data straight from the ODB is NULL-terminated;
		 * copy the contents of a file to a buffer to work on */
		git_buf_put(&content, git_blob_rawcontent(blob), git_blob_rawsize(blob));
127 128 129 130 131
		break;
	}
	case GIT_ATTR_FILE__FROM_FILE: {
		int fd;

132
		/* For open or read errors, pretend that we got ENOTFOUND. */
133 134
		/* TODO: issue warning when warning API is available */

135 136 137 138 139 140 141
		if (p_stat(entry->fullpath, &st) < 0 ||
			S_ISDIR(st.st_mode) ||
			(fd = git_futils_open_ro(entry->fullpath)) < 0 ||
			(error = git_futils_readbuffer_fd(&content, fd, (size_t)st.st_size)) < 0)
			nonexistent = true;
		else
			p_close(fd);
142 143 144 145 146 147

		break;
	}
	default:
		giterr_set(GITERR_INVALID, "Unknown file source %d", source);
		return -1;
148 149
	}

150
	if ((error = git_attr_file__new(&file, entry, source)) < 0)
151 152
		goto cleanup;

153 154 155 156 157 158
	/* store the key of the attr_reader; don't bother with cache
	 * invalidation during the same attr reader session.
	 */
	if (attr_session)
		file->session_key = attr_session->key;

159
	if (parser && (error = parser(repo, file, git_buf_cstr(&content))) < 0) {
160
		git_attr_file__free(file);
161 162 163
		goto cleanup;
	}

164 165 166 167
	/* write cache breakers */
	if (nonexistent)
		file->nonexistent = 1;
	else if (source == GIT_ATTR_FILE__FROM_INDEX)
168 169 170 171 172 173
		git_oid_cpy(&file->cache_data.oid, git_blob_id(blob));
	else if (source == GIT_ATTR_FILE__FROM_FILE)
		git_futils_filestamp_set_from_stat(&file->cache_data.stamp, &st);
	/* else always cacheable */

	*out = file;
174 175 176 177 178 179

cleanup:
	git_blob_free(blob);
	git_buf_free(&content);

	return error;
180 181
}

182 183 184 185
int git_attr_file__out_of_date(
	git_repository *repo,
	git_attr_session *attr_session,
	git_attr_file *file)
186 187 188 189
{
	if (!file)
		return 1;

190 191 192 193 194 195 196 197
	/* we are never out of date if we just created this data in the same
	 * attr_session; otherwise, nonexistent files must be invalidated
	 */
	if (attr_session && attr_session->key == file->session_key)
		return 0;
	else if (file->nonexistent)
		return 1;

198 199 200 201 202 203 204 205 206
	switch (file->source) {
	case GIT_ATTR_FILE__IN_MEMORY:
		return 0;

	case GIT_ATTR_FILE__FROM_FILE:
		return git_futils_filestamp_check(
			&file->cache_data.stamp, file->entry->fullpath);

	case GIT_ATTR_FILE__FROM_INDEX: {
207 208 209
		int error;
		git_oid id;

210 211
		if ((error = attr_file_oid_from_index(
				&id, repo, file->entry->path)) < 0)
212 213 214 215 216
			return error;

		return (git_oid__cmp(&file->cache_data.oid, &id) != 0);
	}

217 218 219 220
	default:
		giterr_set(GITERR_INVALID, "Invalid file type %d", file->source);
		return -1;
	}
221 222 223 224 225 226 227 228 229
}

static int sort_by_hash_and_name(const void *a_raw, const void *b_raw);
static void git_attr_rule__clear(git_attr_rule *rule);
static bool parse_optimized_patterns(
	git_attr_fnmatch *spec,
	git_pool *pool,
	const char *pattern);

230
int git_attr_file__parse_buffer(
231
	git_repository *repo, git_attr_file *attrs, const char *data)
232
{
233
	int error = 0;
234
	const char *scan = data, *context = NULL;
235 236
	git_attr_rule *rule = NULL;

237
	/* if subdir file path, convert context for file paths */
238 239 240 241
	if (attrs->entry &&
		git_path_root(attrs->entry->path) < 0 &&
		!git__suffixcmp(attrs->entry->path, "/" GIT_ATTR_FILE))
		context = attrs->entry->path;
242

243 244 245 246 247
	if (git_mutex_lock(&attrs->lock) < 0) {
		giterr_set(GITERR_OS, "Failed to lock attribute file");
		return -1;
	}

248
	while (!error && *scan) {
249
		/* allocate rule if needed */
250 251 252
		if (!rule && !(rule = git__calloc(1, sizeof(*rule)))) {
			error = -1;
			break;
253 254
		}

255 256 257
		rule->match.flags =
			GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO;

258
		/* parse the next "pattern attr attr attr" line */
259
		if (!(error = git_attr_fnmatch__parse(
260
				&rule->match, &attrs->pool, context, &scan)) &&
261
			!(error = git_attr_assignment__parse(
262
				repo, &attrs->pool, &rule->assigns, &scan)))
263 264
		{
			if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO)
265
				/* TODO: warning if macro found in file below repo root */
266 267 268 269
				error = git_attr_cache__insert_macro(repo, rule);
			else
				error = git_vector_insert(&attrs->rules, rule);
		}
270 271

		/* if the rule wasn't a pattern, on to the next */
272
		if (error < 0) {
Russell Belfer committed
273
			git_attr_rule__clear(rule); /* reset rule contents */
274
			if (error == GIT_ENOTFOUND)
275
				error = 0;
276 277 278 279 280
		} else {
			rule = NULL; /* vector now "owns" the rule */
		}
	}

281
	git_mutex_unlock(&attrs->lock);
282
	git_attr_rule__free(rule);
283

284 285 286
	return error;
}

287
uint32_t git_attr_file__name_hash(const char *name)
288
{
289
	uint32_t h = 5381;
290 291 292 293 294 295 296 297 298
	int c;
	assert(name);
	while ((c = (int)*name++) != 0)
		h = ((h << 5) + h) + c;
	return h;
}

int git_attr_file__lookup_one(
	git_attr_file *file,
299
	git_attr_path *path,
300 301 302
	const char *attr,
	const char **value)
{
303
	size_t i;
304 305 306 307 308 309 310 311 312
	git_attr_name name;
	git_attr_rule *rule;

	*value = NULL;

	name.name = attr;
	name.name_hash = git_attr_file__name_hash(attr);

	git_attr_file__foreach_matching_rule(file, path, i, rule) {
313
		size_t pos;
314

315
		if (!git_vector_bsearch(&pos, &rule->assigns, &name)) {
316 317 318 319 320 321
			*value = ((git_attr_assignment *)
					  git_vector_get(&rule->assigns, pos))->value;
			break;
		}
	}

322
	return 0;
323 324
}

325
int git_attr_file__load_standalone(git_attr_file **out, const char *path)
326 327 328 329 330
{
	int error;
	git_attr_file *file;
	git_buf content = GIT_BUF_INIT;

331
	error = git_attr_file__new(&file, NULL, GIT_ATTR_FILE__FROM_FILE);
332 333 334
	if (error < 0)
		return error;

335 336
	error = git_attr_cache__alloc_file_entry(
		&file->entry, NULL, path, &file->pool);
337 338 339 340 341 342 343 344 345
	if (error < 0) {
		git_attr_file__free(file);
		return error;
	}
	/* because the cache entry is allocated from the file's own pool, we
	 * don't have to free it - freeing file+pool will free cache entry, too.
	 */

	if (!(error = git_futils_readbuffer(&content, path))) {
346
		error = git_attr_file__parse_buffer(NULL, file, content.ptr);
347 348 349 350 351 352 353 354 355 356
		git_buf_free(&content);
	}

	if (error < 0)
		git_attr_file__free(file);
	else
		*out = file;

	return error;
}
357

358
bool git_attr_fnmatch__match(
359
	git_attr_fnmatch *match,
360
	git_attr_path *path)
361
{
362
	const char *relpath = path->path;
363 364
	const char *filename;
	int flags = 0;
365

366 367 368 369 370 371 372 373 374 375 376 377 378
	/*
	 * If the rule was generated in a subdirectory, we must only
	 * use it for paths inside that directory. We can thus return
	 * a non-match if the prefixes don't match.
	 */
	if (match->containing_dir) {
		if (match->flags & GIT_ATTR_FNMATCH_ICASE) {
			if (git__strncasecmp(path->path, match->containing_dir, match->containing_dir_length))
				return 0;
		} else {
			if (git__prefixcmp(path->path, match->containing_dir))
				return 0;
		}
379 380

		relpath += match->containing_dir_length;
381 382
	}

383 384
	if (match->flags & GIT_ATTR_FNMATCH_ICASE)
		flags |= FNM_CASEFOLD;
385 386
	if (match->flags & GIT_ATTR_FNMATCH_LEADINGDIR)
		flags |= FNM_LEADING_DIR;
387 388

	if (match->flags & GIT_ATTR_FNMATCH_FULLPATH) {
389
		filename = relpath;
390 391 392 393 394 395 396
		flags |= FNM_PATHNAME;
	} else {
		filename = path->basename;

		if (path->is_dir)
			flags |= FNM_LEADING_DIR;
	}
397

398
	if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir) {
399 400
		bool samename;

401 402 403 404 405 406
		/* for attribute checks or root ignore checks, fail match */
		if (!(match->flags & GIT_ATTR_FNMATCH_IGNORE) ||
			path->basename == path->path)
			return false;

		flags |= FNM_LEADING_DIR;
407

408
		/* fail match if this is a file with same name as ignored folder */
409
		samename = (match->flags & GIT_ATTR_FNMATCH_ICASE) ?
410 411
			!strcasecmp(match->pattern, relpath) :
			!strcmp(match->pattern, relpath);
412 413 414 415

		if (samename)
			return false;

416
		return (p_fnmatch(match->pattern, relpath, flags) != FNM_NOMATCH);
417 418
	}

419 420
	/* if path is a directory prefix of a negated pattern, then match */
	if ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) && path->is_dir) {
421
		size_t pathlen = strlen(relpath);
422 423
		bool prefixed = (pathlen <= match->length) &&
			((match->flags & GIT_ATTR_FNMATCH_ICASE) ?
424 425
			!strncasecmp(match->pattern, relpath, pathlen) :
			!strncmp(match->pattern, relpath, pathlen));
426 427 428 429 430

		if (prefixed && git_path_at_end_of_segment(&match->pattern[pathlen]))
			return true;
	}

431
	return (p_fnmatch(match->pattern, filename, flags) != FNM_NOMATCH);
432 433
}

434
bool git_attr_rule__match(
435
	git_attr_rule *rule,
436
	git_attr_path *path)
437
{
438
	bool matched = git_attr_fnmatch__match(&rule->match, path);
439

440
	if (rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE)
441
		matched = !matched;
442 443 444 445 446 447 448

	return matched;
}

git_attr_assignment *git_attr_rule__lookup_assignment(
	git_attr_rule *rule, const char *name)
{
449
	size_t pos;
450 451 452 453
	git_attr_name key;
	key.name = name;
	key.name_hash = git_attr_file__name_hash(name);

454 455
	if (git_vector_bsearch(&pos, &rule->assigns, &key))
		return NULL;
456

457
	return git_vector_get(&rule->assigns, pos);
458 459 460
}

int git_attr_path__init(
461
	git_attr_path *info, const char *path, const char *base, git_dir_flag dir_flag)
462
{
463 464
	ssize_t root;

465 466
	/* build full path as best we can */
	git_buf_init(&info->full, 0);
467

468 469 470 471
	if (git_path_join_unrooted(&info->full, path, base, &root) < 0)
		return -1;

	info->path = info->full.ptr + root;
472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491

	/* remove trailing slashes */
	while (info->full.size > 0) {
		if (info->full.ptr[info->full.size - 1] != '/')
			break;
		info->full.size--;
	}
	info->full.ptr[info->full.size] = '\0';

	/* skip leading slashes in path */
	while (*info->path == '/')
		info->path++;

	/* find trailing basename component */
	info->basename = strrchr(info->path, '/');
	if (info->basename)
		info->basename++;
	if (!info->basename || !*info->basename)
		info->basename = info->path;

492 493 494 495 496 497 498 499 500 501 502 503 504 505 506
	switch (dir_flag)
	{
	case GIT_DIR_FLAG_FALSE:
		info->is_dir = 0;
		break;

	case GIT_DIR_FLAG_TRUE:
		info->is_dir = 1;
		break;

	case GIT_DIR_FLAG_UNKNOWN:
	default:
		info->is_dir = (int)git_path_isdir(info->full.ptr);
		break;
	}
507

508
	return 0;
509 510
}

511 512 513 514 515 516 517
void git_attr_path__free(git_attr_path *info)
{
	git_buf_free(&info->full);
	info->path = NULL;
	info->basename = NULL;
}

518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550
/*
 * From gitattributes(5):
 *
 * Patterns have the following format:
 *
 * - A blank line matches no files, so it can serve as a separator for
 *   readability.
 *
 * - A line starting with # serves as a comment.
 *
 * - An optional prefix ! which negates the pattern; any matching file
 *   excluded by a previous pattern will become included again. If a negated
 *   pattern matches, this will override lower precedence patterns sources.
 *
 * - If the pattern ends with a slash, it is removed for the purpose of the
 *   following description, but it would only find a match with a directory. In
 *   other words, foo/ will match a directory foo and paths underneath it, but
 *   will not match a regular file or a symbolic link foo (this is consistent
 *   with the way how pathspec works in general in git).
 *
 * - If the pattern does not contain a slash /, git treats it as a shell glob
 *   pattern and checks for a match against the pathname without leading
 *   directories.
 *
 * - Otherwise, git treats the pattern as a shell glob suitable for consumption
 *   by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will
 *   not match a / in the pathname. For example, "Documentation/\*.html" matches
 *   "Documentation/git.html" but not "Documentation/ppc/ppc.html". A leading
 *   slash matches the beginning of the pathname; for example, "/\*.c" matches
 *   "cat-file.c" but not "mozilla-sha1/sha1.c".
 */

/*
551
 * This will return 0 if the spec was filled out,
552 553 554
 * GIT_ENOTFOUND if the fnmatch does not require matching, or
 * another error code there was an actual problem.
 */
555
int git_attr_fnmatch__parse(
556
	git_attr_fnmatch *spec,
557
	git_pool *pool,
558
	const char *context,
559 560
	const char **base)
{
561 562
	const char *pattern, *scan;
	int slash_count, allow_space;
563

564
	assert(spec && base && *base);
565

566 567 568 569 570 571
	if (parse_optimized_patterns(spec, pool, *base))
		return 0;

	spec->flags = (spec->flags & GIT_ATTR_FNMATCH__INCOMING);
	allow_space = ((spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE) != 0);

572 573
	pattern = *base;

574
	while (git__isspace(*pattern)) pattern++;
575
	if (!*pattern || *pattern == '#') {
576 577
		*base = git__next_line(pattern);
		return GIT_ENOTFOUND;
578 579
	}

580
	if (*pattern == '[' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWMACRO) != 0) {
581 582 583 584
		if (strncmp(pattern, "[attr]", 6) == 0) {
			spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO;
			pattern += 6;
		}
585
		/* else a character range like [a-e]* which is accepted */
586 587
	}

588
	if (*pattern == '!' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWNEG) != 0) {
589 590
		spec->flags = spec->flags |
			GIT_ATTR_FNMATCH_NEGATIVE | GIT_ATTR_FNMATCH_LEADINGDIR;
591 592 593 594 595
		pattern++;
	}

	slash_count = 0;
	for (scan = pattern; *scan != '\0'; ++scan) {
596
		/* scan until (non-escaped) white space */
597
		if (git__isspace(*scan) && *(scan - 1) != '\\') {
598
			if (!allow_space || (*scan != ' ' && *scan != '\t' && *scan != '\r'))
599 600
				break;
		}
601 602

		if (*scan == '/') {
603
			spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH;
604
			slash_count++;
605 606
			if (pattern == scan)
				pattern++;
607
		}
608
		/* remember if we see an unescaped wildcard in pattern */
609
		else if (git__iswildcard(*scan) &&
610 611
			(scan == pattern || (*(scan - 1) != '\\')))
			spec->flags = spec->flags | GIT_ATTR_FNMATCH_HASWILD;
612 613 614
	}

	*base = scan;
615

616 617
	if ((spec->length = scan - pattern) == 0)
		return GIT_ENOTFOUND;
618

619 620 621 622 623 624 625 626 627
	/*
	 * Remove one trailing \r in case this is a CRLF delimited
	 * file, in the case of Icon\r\r\n, we still leave the first
	 * \r there to match against.
	 */
	if (pattern[spec->length - 1] == '\r')
		if (--spec->length == 0)
			return GIT_ENOTFOUND;

628 629
	if (pattern[spec->length - 1] == '/') {
		spec->length--;
630
		spec->flags = spec->flags | GIT_ATTR_FNMATCH_DIRECTORY;
631
		if (--slash_count <= 0)
632
			spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH;
633
	}
Russell Belfer committed
634 635
	if ((spec->flags & GIT_ATTR_FNMATCH_NOLEADINGDIR) == 0 &&
		spec->length >= 2 &&
636 637 638 639 640 641
		pattern[spec->length - 1] == '*' &&
		pattern[spec->length - 2] == '/') {
		spec->length -= 2;
		spec->flags = spec->flags | GIT_ATTR_FNMATCH_LEADINGDIR;
		/* leave FULLPATH match on, however */
	}
642

643
	if (context) {
644
		char *slash = strrchr(context, '/');
645 646 647 648 649 650 651 652 653
		size_t len;
		if (slash) {
			/* include the slash for easier matching */
			len = slash - context + 1;
			spec->containing_dir = git_pool_strndup(pool, context, len);
			spec->containing_dir_length = len;
		}
	}

654
	spec->pattern = git_pool_strndup(pool, pattern, spec->length);
655 656 657

	if (!spec->pattern) {
		*base = git__next_line(pattern);
658
		return -1;
659
	} else {
660
		/* strip '\' that might have be used for internal whitespace */
661
		spec->length = git__unescape(spec->pattern);
662
		/* TODO: convert remaining '\' into '/' for POSIX ??? */
663 664
	}

665
	return 0;
666 667
}

668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683
static bool parse_optimized_patterns(
	git_attr_fnmatch *spec,
	git_pool *pool,
	const char *pattern)
{
	if (!pattern[1] && (pattern[0] == '*' || pattern[0] == '.')) {
		spec->flags = GIT_ATTR_FNMATCH_MATCH_ALL;
		spec->pattern = git_pool_strndup(pool, pattern, 1);
		spec->length = 1;

		return true;
	}

	return false;
}

684 685 686 687 688 689 690 691 692 693 694 695 696
static int sort_by_hash_and_name(const void *a_raw, const void *b_raw)
{
	const git_attr_name *a = a_raw;
	const git_attr_name *b = b_raw;

	if (b->name_hash < a->name_hash)
		return 1;
	else if (b->name_hash > a->name_hash)
		return -1;
	else
		return strcmp(b->name, a->name);
}

697
static void git_attr_assignment__free(git_attr_assignment *assign)
698
{
699 700 701
	/* name and value are stored in a git_pool associated with the
	 * git_attr_file, so they do not need to be freed here
	 */
702
	assign->name = NULL;
703
	assign->value = NULL;
704 705 706
	git__free(assign);
}

707 708 709 710 711 712 713 714 715 716
static int merge_assignments(void **old_raw, void *new_raw)
{
	git_attr_assignment **old = (git_attr_assignment **)old_raw;
	git_attr_assignment *new = (git_attr_assignment *)new_raw;

	GIT_REFCOUNT_DEC(*old, git_attr_assignment__free);
	*old = new;
	return GIT_EEXISTS;
}

717 718
int git_attr_assignment__parse(
	git_repository *repo,
719
	git_pool *pool,
720 721 722
	git_vector *assigns,
	const char **base)
{
723
	int error;
724 725 726 727 728
	const char *scan = *base;
	git_attr_assignment *assign = NULL;

	assert(assigns && !assigns->length);

729
	git_vector_set_cmp(assigns, sort_by_hash_and_name);
730

731
	while (*scan && *scan != '\n') {
732 733 734
		const char *name_start, *value_start;

		/* skip leading blanks */
735
		while (git__isspace(*scan) && *scan != '\n') scan++;
736 737 738 739

		/* allocate assign if needed */
		if (!assign) {
			assign = git__calloc(1, sizeof(git_attr_assignment));
740
			GITERR_CHECK_ALLOC(assign);
741
			GIT_REFCOUNT_INC(assign);
742 743 744
		}

		assign->name_hash = 5381;
745
		assign->value = git_attr__true;
746 747 748

		/* look for magic name prefixes */
		if (*scan == '-') {
749
			assign->value = git_attr__false;
750 751
			scan++;
		} else if (*scan == '!') {
752
			assign->value = git_attr__unset; /* explicit unspecified state */
753 754 755 756 757 758
			scan++;
		} else if (*scan == '#') /* comment rest of line */
			break;

		/* find the name */
		name_start = scan;
759
		while (*scan && !git__isspace(*scan) && *scan != '=') {
760 761 762 763
			assign->name_hash =
				((assign->name_hash << 5) + assign->name_hash) + *scan;
			scan++;
		}
764
		if (scan == name_start) {
765 766 767
			/* must have found lone prefix (" - ") or leading = ("=foo")
			 * or end of buffer -- advance until whitespace and continue
			 */
768
			while (*scan && !git__isspace(*scan)) scan++;
769 770 771
			continue;
		}

772
		/* allocate permanent storage for name */
773
		assign->name = git_pool_strndup(pool, name_start, scan - name_start);
774
		GITERR_CHECK_ALLOC(assign->name);
775

776 777
		/* if there is an equals sign, find the value */
		if (*scan == '=') {
778
			for (value_start = ++scan; *scan && !git__isspace(*scan); ++scan);
779 780 781

			/* if we found a value, allocate permanent storage for it */
			if (scan > value_start) {
782
				assign->value = git_pool_strndup(pool, value_start, scan - value_start);
783
				GITERR_CHECK_ALLOC(assign->value);
784 785 786
			}
		}

787
		/* expand macros (if given a repo with a macro cache) */
788
		if (repo != NULL && assign->value == git_attr__true) {
789 790
			git_attr_rule *macro =
				git_attr_cache__lookup_macro(repo, assign->name);
791 792 793 794 795

			if (macro != NULL) {
				unsigned int i;
				git_attr_assignment *massign;

796 797
				git_vector_foreach(&macro->assigns, i, massign) {
					GIT_REFCOUNT_INC(massign);
798

799 800
					error = git_vector_insert_sorted(
						assigns, massign, &merge_assignments);
Jacques Germishuys committed
801 802
					if (error < 0 && error != GIT_EEXISTS) {
						git_attr_assignment__free(assign);
803
						return error;
Jacques Germishuys committed
804
					}
805 806
				}
			}
807 808 809
		}

		/* insert allocated assign into vector */
810
		error = git_vector_insert_sorted(assigns, assign, &merge_assignments);
811 812
		if (error < 0 && error != GIT_EEXISTS)
			return error;
813 814 815 816 817

		/* clear assign since it is now "owned" by the vector */
		assign = NULL;
	}

818
	if (assign != NULL)
819
		git_attr_assignment__free(assign);
820

821
	*base = git__next_line(scan);
822

823
	return (assigns->length == 0) ? GIT_ENOTFOUND : 0;
824 825
}

Russell Belfer committed
826
static void git_attr_rule__clear(git_attr_rule *rule)
827 828 829 830 831
{
	unsigned int i;
	git_attr_assignment *assign;

	if (!rule)
832
		return;
833

834 835 836 837 838 839
	if (!(rule->match.flags & GIT_ATTR_FNMATCH_IGNORE)) {
		git_vector_foreach(&rule->assigns, i, assign)
			GIT_REFCOUNT_DEC(assign, git_attr_assignment__free);
		git_vector_free(&rule->assigns);
	}

840
	/* match.pattern is stored in a git_pool, so no need to free */
841 842 843
	rule->match.pattern = NULL;
	rule->match.length = 0;
}
Russell Belfer committed
844 845 846 847 848 849 850

void git_attr_rule__free(git_attr_rule *rule)
{
	git_attr_rule__clear(rule);
	git__free(rule);
}

851 852 853 854 855 856 857 858
int git_attr_session__init(git_attr_session *session, git_repository *repo)
{
	assert(repo);

	session->key = git_atomic_inc(&repo->attr_session_key);

	return 0;
}
859 860 861 862 863 864 865

void git_attr_session__free(git_attr_session *session)
{
	if (!session)
		return;

	git_buf_free(&session->sysdir);
866
	git_buf_free(&session->tmp);
867 868 869

	memset(session, 0, sizeof(git_attr_session));
}