attr_file.c 21.1 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
	git_pool_init(&attrs->pool, 1);
39
	GIT_REFCOUNT_INC(attrs);
40
	attrs->entry  = entry;
41 42 43 44
	attrs->source = source;
	*out = attrs;
	return 0;
}
45

46
int git_attr_file__clear_rules(git_attr_file *file, bool need_lock)
47 48 49
{
	unsigned int i;
	git_attr_rule *rule;
50

51 52 53 54 55
	if (need_lock && git_mutex_lock(&file->lock) < 0) {
		giterr_set(GITERR_OS, "Failed to lock attribute file");
		return -1;
	}

56 57 58
	git_vector_foreach(&file->rules, i, rule)
		git_attr_rule__free(rule);
	git_vector_free(&file->rules);
59 60 61 62 63

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

	return 0;
64
}
65

66 67 68 69 70 71
void git_attr_file__free(git_attr_file *file)
{
	if (!file)
		return;
	GIT_REFCOUNT_DEC(file, attr_file_free);
}
72

73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
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;
89
	return 0;
90 91 92 93 94
}

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

	*out = NULL;

109 110 111 112 113
	switch (source) {
	case GIT_ATTR_FILE__IN_MEMORY:
		/* in-memory attribute file doesn't need data */
		break;
	case GIT_ATTR_FILE__FROM_INDEX: {
114
		git_oid id;
115

116
		if ((error = attr_file_oid_from_index(&id, repo, entry->path)) < 0 ||
117 118 119
			(error = git_blob_lookup(&blob, repo, &id)) < 0)
			return error;

120 121 122
		/* 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));
123 124 125
		break;
	}
	case GIT_ATTR_FILE__FROM_FILE: {
126
		int fd = -1;
127

128
		/* For open or read errors, pretend that we got ENOTFOUND. */
129 130
		/* TODO: issue warning when warning API is available */

131 132 133 134 135
		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;
136 137

		if (fd >= 0)
138
			p_close(fd);
139 140 141 142 143 144

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

147
	if ((error = git_attr_file__new(&file, entry, source)) < 0)
148 149
		goto cleanup;

150 151 152 153 154 155
	/* 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;

156
	if (parser && (error = parser(repo, file, git_buf_cstr(&content))) < 0) {
157
		git_attr_file__free(file);
158 159 160
		goto cleanup;
	}

161 162 163 164
	/* write cache breakers */
	if (nonexistent)
		file->nonexistent = 1;
	else if (source == GIT_ATTR_FILE__FROM_INDEX)
165 166 167 168 169 170
		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;
171 172 173 174 175 176

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

	return error;
177 178
}

179 180 181 182
int git_attr_file__out_of_date(
	git_repository *repo,
	git_attr_session *attr_session,
	git_attr_file *file)
183 184 185 186
{
	if (!file)
		return 1;

187 188 189 190 191 192 193 194
	/* 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;

195 196 197 198 199 200 201 202 203
	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: {
204 205 206
		int error;
		git_oid id;

207 208
		if ((error = attr_file_oid_from_index(
				&id, repo, file->entry->path)) < 0)
209 210 211 212 213
			return error;

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

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

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);

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

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

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

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

252 253 254
		rule->match.flags =
			GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO;

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

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

278
	git_mutex_unlock(&attrs->lock);
279
	git_attr_rule__free(rule);
280

281 282 283
	return error;
}

284
uint32_t git_attr_file__name_hash(const char *name)
285
{
286
	uint32_t h = 5381;
287 288 289 290 291 292 293 294 295
	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,
296
	git_attr_path *path,
297 298 299
	const char *attr,
	const char **value)
{
300
	size_t i;
301 302 303 304 305 306 307 308 309
	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) {
310
		size_t pos;
311

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

319
	return 0;
320 321
}

322
int git_attr_file__load_standalone(git_attr_file **out, const char *path)
323 324 325 326 327
{
	int error;
	git_attr_file *file;
	git_buf content = GIT_BUF_INIT;

328
	error = git_attr_file__new(&file, NULL, GIT_ATTR_FILE__FROM_FILE);
329 330 331
	if (error < 0)
		return error;

332 333
	error = git_attr_cache__alloc_file_entry(
		&file->entry, NULL, path, &file->pool);
334 335 336 337 338 339 340 341 342
	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))) {
343
		error = git_attr_file__parse_buffer(NULL, file, content.ptr);
344 345 346 347 348 349 350 351 352 353
		git_buf_free(&content);
	}

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

	return error;
}
354

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

363 364 365 366 367 368 369 370 371 372 373 374 375
	/*
	 * 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;
		}
376 377

		relpath += match->containing_dir_length;
378 379
	}

380 381
	if (match->flags & GIT_ATTR_FNMATCH_ICASE)
		flags |= FNM_CASEFOLD;
382 383
	if (match->flags & GIT_ATTR_FNMATCH_LEADINGDIR)
		flags |= FNM_LEADING_DIR;
384 385

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

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

395
	if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir) {
396 397
		bool samename;

398 399 400 401 402 403
		/* 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;
404

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

		if (samename)
			return false;

413
		return (p_fnmatch(match->pattern, relpath, flags) != FNM_NOMATCH);
414 415
	}

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

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

428
	return (p_fnmatch(match->pattern, filename, flags) != FNM_NOMATCH);
429 430
}

431
bool git_attr_rule__match(
432
	git_attr_rule *rule,
433
	git_attr_path *path)
434
{
435
	bool matched = git_attr_fnmatch__match(&rule->match, path);
436

437
	if (rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE)
438
		matched = !matched;
439 440 441 442 443 444 445

	return matched;
}

git_attr_assignment *git_attr_rule__lookup_assignment(
	git_attr_rule *rule, const char *name)
{
446
	size_t pos;
447 448 449 450
	git_attr_name key;
	key.name = name;
	key.name_hash = git_attr_file__name_hash(name);

451 452
	if (git_vector_bsearch(&pos, &rule->assigns, &key))
		return NULL;
453

454
	return git_vector_get(&rule->assigns, pos);
455 456 457
}

int git_attr_path__init(
458
	git_attr_path *info, const char *path, const char *base, git_dir_flag dir_flag)
459
{
460 461
	ssize_t root;

462 463
	/* build full path as best we can */
	git_buf_init(&info->full, 0);
464

465 466 467 468
	if (git_path_join_unrooted(&info->full, path, base, &root) < 0)
		return -1;

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

	/* 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;

489 490 491 492 493 494 495 496 497 498 499 500 501 502 503
	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;
	}
504

505
	return 0;
506 507
}

508 509 510 511 512 513 514
void git_attr_path__free(git_attr_path *info)
{
	git_buf_free(&info->full);
	info->path = NULL;
	info->basename = NULL;
}

515 516 517 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
/*
 * 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".
 */

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

561
	assert(spec && base && *base);
562

563 564 565 566 567 568
	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);

569 570
	pattern = *base;

571
	while (git__isspace(*pattern)) pattern++;
572
	if (!*pattern || *pattern == '#') {
573 574
		*base = git__next_line(pattern);
		return GIT_ENOTFOUND;
575 576
	}

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

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

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

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

	*base = scan;
612

613 614
	if ((spec->length = scan - pattern) == 0)
		return GIT_ENOTFOUND;
615

616 617 618 619 620 621 622 623 624
	/*
	 * 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;

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

640
	if (context) {
641
		char *slash = strrchr(context, '/');
642 643 644 645 646 647 648 649 650
		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;
		}
	}

651
	spec->pattern = git_pool_strndup(pool, pattern, spec->length);
652 653 654

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

662
	return 0;
663 664
}

665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680
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;
}

681 682 683 684 685 686 687 688 689 690 691 692 693
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);
}

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

704 705 706 707 708 709 710 711 712 713
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;
}

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

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

726
	git_vector_set_cmp(assigns, sort_by_hash_and_name);
727

728
	while (*scan && *scan != '\n') {
729 730 731
		const char *name_start, *value_start;

		/* skip leading blanks */
732
		while (git__isspace(*scan) && *scan != '\n') scan++;
733 734 735 736

		/* allocate assign if needed */
		if (!assign) {
			assign = git__calloc(1, sizeof(git_attr_assignment));
737
			GITERR_CHECK_ALLOC(assign);
738
			GIT_REFCOUNT_INC(assign);
739 740 741
		}

		assign->name_hash = 5381;
742
		assign->value = git_attr__true;
743 744 745

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

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

769
		/* allocate permanent storage for name */
770
		assign->name = git_pool_strndup(pool, name_start, scan - name_start);
771
		GITERR_CHECK_ALLOC(assign->name);
772

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

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

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

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

793 794
				git_vector_foreach(&macro->assigns, i, massign) {
					GIT_REFCOUNT_INC(massign);
795

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

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

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

815
	if (assign != NULL)
816
		git_attr_assignment__free(assign);
817

818
	*base = git__next_line(scan);
819

820
	return (assigns->length == 0) ? GIT_ENOTFOUND : 0;
821 822
}

Russell Belfer committed
823
static void git_attr_rule__clear(git_attr_rule *rule)
824 825 826 827 828
{
	unsigned int i;
	git_attr_assignment *assign;

	if (!rule)
829
		return;
830

831 832 833 834 835 836
	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);
	}

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

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

848 849 850 851 852 853 854 855
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;
}
856 857 858 859 860 861 862

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

	git_buf_free(&session->sysdir);
863
	git_buf_free(&session->tmp);
864 865 866

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