attr.c 16.3 KB
Newer Older
1
#include "repository.h"
2 3
#include "fileops.h"
#include "config.h"
4 5
#include "attr.h"
#include "ignore.h"
6
#include "git2/oid.h"
7 8
#include <ctype.h>

9
GIT__USE_STRMAP;
10

11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
const char *git_attr__true  = "[internal]__TRUE__";
const char *git_attr__false = "[internal]__FALSE__";
const char *git_attr__unset = "[internal]__UNSET__";

git_attr_t git_attr_value(const char *attr)
{
	if (attr == NULL || attr == git_attr__unset)
		return GIT_ATTR_UNSPECIFIED_T;

	if (attr == git_attr__true)
		return GIT_ATTR_TRUE_T;

	if (attr == git_attr__false)
		return GIT_ATTR_FALSE_T;

	return GIT_ATTR_VALUE_T;
}

29
static int collect_attr_files(
30 31 32 33
	git_repository *repo,
	uint32_t flags,
	const char *path,
	git_vector *files);
34 35 36


int git_attr_get(
37
	const char **value,
Linquize committed
38
	git_repository *repo,
39 40
	uint32_t flags,
	const char *pathname,
41
	const char *name)
42 43 44 45
{
	int error;
	git_attr_path path;
	git_vector files = GIT_VECTOR_INIT;
46
	size_t i, j;
47 48 49 50 51 52
	git_attr_file *file;
	git_attr_name attr;
	git_attr_rule *rule;

	*value = NULL;

53 54 55
	if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
		return -1;

56
	if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0)
57
		goto cleanup;
58 59 60 61 62 63 64

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

	git_vector_foreach(&files, i, file) {

		git_attr_file__foreach_matching_rule(file, &path, j, rule) {
65 66 67
			size_t pos;

			if (!git_vector_bsearch(&pos, &rule->assigns, &attr)) {
68 69
				*value = ((git_attr_assignment *)git_vector_get(
							  &rule->assigns, pos))->value;
70
				goto cleanup;
71 72 73 74
			}
		}
	}

75
cleanup:
76
	git_vector_free(&files);
77
	git_attr_path__free(&path);
78 79 80 81 82 83 84 85 86 87 88

	return error;
}


typedef struct {
	git_attr_name name;
	git_attr_assignment *found;
} attr_get_many_info;

int git_attr_get_many(
89
	const char **values,
Linquize committed
90
	git_repository *repo,
91 92
	uint32_t flags,
	const char *pathname,
Linquize committed
93
	size_t num_attr,
94
	const char **names)
95 96 97 98
{
	int error;
	git_attr_path path;
	git_vector files = GIT_VECTOR_INIT;
99
	size_t i, j, k;
100 101 102 103 104
	git_attr_file *file;
	git_attr_rule *rule;
	attr_get_many_info *info = NULL;
	size_t num_found = 0;

105 106 107
	if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
		return -1;

108
	if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0)
109
		goto cleanup;
110

111 112
	info = git__calloc(num_attr, sizeof(attr_get_many_info));
	GITERR_CHECK_ALLOC(info);
113 114 115 116 117 118

	git_vector_foreach(&files, i, file) {

		git_attr_file__foreach_matching_rule(file, &path, j, rule) {

			for (k = 0; k < num_attr; k++) {
119
				size_t pos;
120 121 122 123 124 125 126 127 128

				if (info[k].found != NULL) /* already found assignment */
					continue;

				if (!info[k].name.name) {
					info[k].name.name = names[k];
					info[k].name.name_hash = git_attr_file__name_hash(names[k]);
				}

129
				if (!git_vector_bsearch(&pos, &rule->assigns, &info[k].name)) {
130 131 132 133 134 135 136 137 138 139 140
					info[k].found = (git_attr_assignment *)
						git_vector_get(&rule->assigns, pos);
					values[k] = info[k].found->value;

					if (++num_found == num_attr)
						goto cleanup;
				}
			}
		}
	}

141 142 143 144 145
	for (k = 0; k < num_attr; k++) {
		if (!info[k].found)
			values[k] = NULL;
	}

146 147
cleanup:
	git_vector_free(&files);
148
	git_attr_path__free(&path);
149 150 151 152 153 154 155
	git__free(info);

	return error;
}


int git_attr_foreach(
Linquize committed
156
	git_repository *repo,
157 158
	uint32_t flags,
	const char *pathname,
159 160 161 162 163 164
	int (*callback)(const char *name, const char *value, void *payload),
	void *payload)
{
	int error;
	git_attr_path path;
	git_vector files = GIT_VECTOR_INIT;
165
	size_t i, j, k;
166 167 168
	git_attr_file *file;
	git_attr_rule *rule;
	git_attr_assignment *assign;
169
	git_strmap *seen = NULL;
170

171 172 173
	if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
		return -1;

174
	if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0)
175
		goto cleanup;
176

177
	seen = git_strmap_alloc();
178
	GITERR_CHECK_ALLOC(seen);
179 180 181 182 183 184 185

	git_vector_foreach(&files, i, file) {

		git_attr_file__foreach_matching_rule(file, &path, j, rule) {

			git_vector_foreach(&rule->assigns, k, assign) {
				/* skip if higher priority assignment was already seen */
186
				if (git_strmap_exists(seen, assign->name))
187 188
					continue;

189
				git_strmap_insert(seen, assign->name, assign, error);
190 191
				if (error < 0)
					goto cleanup;
192

193 194
				error = callback(assign->name, assign->value, payload);
				if (error) {
Russell Belfer committed
195
					giterr_clear();
196
					error = GIT_EUSER;
197
					goto cleanup;
198
				}
199 200 201 202 203
			}
		}
	}

cleanup:
204
	git_strmap_free(seen);
205
	git_vector_free(&files);
206
	git_attr_path__free(&path);
207 208 209 210 211

	return error;
}


212 213 214 215 216 217 218
int git_attr_add_macro(
	git_repository *repo,
	const char *name,
	const char *values)
{
	int error;
	git_attr_rule *macro = NULL;
219
	git_pool *pool;
220

221 222
	if (git_attr_cache__init(repo) < 0)
		return -1;
223 224

	macro = git__calloc(1, sizeof(git_attr_rule));
225
	GITERR_CHECK_ALLOC(macro);
226

227 228 229
	pool = &git_repository_attr_cache(repo)->pool;

	macro->match.pattern = git_pool_strdup(pool, name);
230
	GITERR_CHECK_ALLOC(macro->match.pattern);
231 232 233 234

	macro->match.length = strlen(macro->match.pattern);
	macro->match.flags = GIT_ATTR_FNMATCH_MACRO;

235
	error = git_attr_assignment__parse(repo, pool, &macro->assigns, &values);
236

237
	if (!error)
238 239
		error = git_attr_cache__insert_macro(repo, macro);

240
	if (error < 0)
241 242 243 244 245
		git_attr_rule__free(macro);

	return error;
}

246 247
bool git_attr_cache__is_cached(
	git_repository *repo, git_attr_file_source source, const char *path)
248
{
249
	git_buf cache_key = GIT_BUF_INIT;
250
	git_strmap *files = git_repository_attr_cache(repo)->files;
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
	const char *workdir = git_repository_workdir(repo);
	bool rval;

	if (workdir && git__prefixcmp(path, workdir) == 0)
		path += strlen(workdir);
	if (git_buf_printf(&cache_key, "%d#%s", (int)source, path) < 0)
		return false;

	rval = git_strmap_exists(files, git_buf_cstr(&cache_key));

	git_buf_free(&cache_key);

	return rval;
}

266 267
static int load_attr_file(
	const char **data,
Vicent Marti committed
268
	git_futils_filestamp *stamp,
269
	const char *filename)
270 271 272 273
{
	int error;
	git_buf content = GIT_BUF_INIT;

Vicent Marti committed
274
	error = git_futils_filestamp_check(stamp, filename);
275 276
	if (error < 0)
		return error;
277

278 279 280 281
	/* if error == 0, then file is up to date. By returning GIT_ENOTFOUND,
	 * we tell the caller not to reparse this file...
	 */
	if (!error)
282
		return GIT_ENOTFOUND;
283

284
	error = git_futils_readbuffer(&content, filename);
285 286 287 288 289 290 291 292
	if (error < 0) {
		/* convert error into ENOTFOUND so failed permissions / invalid
		 * file type don't actually stop the operation in progress.
		 */
		return GIT_ENOTFOUND;

		/* TODO: once warnings are available, issue a warning callback */
	}
293 294 295 296

	*data = git_buf_detach(&content);

	return 0;
297
}
298

299
static int load_attr_blob_from_index(
300 301 302 303 304
	const char **content,
	git_blob **blob,
	git_repository *repo,
	const git_oid *old_oid,
	const char *relfile)
305 306
{
	int error;
307
	size_t pos;
308
	git_index *index;
Ben Straub committed
309
	const git_index_entry *entry;
310 311

	if ((error = git_repository_index__weakptr(&index, repo)) < 0 ||
312
		(error = git_index_find(&pos, index, relfile)) < 0)
313
		return error;
314

315
	entry = git_index_get_byindex(index, pos);
316

317
	if (old_oid && git_oid__cmp(old_oid, &entry->oid) == 0)
318
		return GIT_ENOTFOUND;
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351

	if ((error = git_blob_lookup(blob, repo, &entry->oid)) < 0)
		return error;

	*content = git_blob_rawcontent(*blob);
	return 0;
}

static int load_attr_from_cache(
	git_attr_file **file,
	git_attr_cache *cache,
	git_attr_file_source source,
	const char *relative_path)
{
	git_buf  cache_key = GIT_BUF_INIT;
	khiter_t cache_pos;

	*file = NULL;

	if (!cache || !cache->files)
		return 0;

	if (git_buf_printf(&cache_key, "%d#%s", (int)source, relative_path) < 0)
		return -1;

	cache_pos = git_strmap_lookup_index(cache->files, cache_key.ptr);

	git_buf_free(&cache_key);

	if (git_strmap_valid_index(cache->files, cache_pos))
		*file = git_strmap_value_at(cache->files, cache_pos);

	return 0;
352
}
353

354
int git_attr_cache__internal_file(
355 356
	git_repository *repo,
	const char *filename,
357
	git_attr_file **file)
358
{
359
	int error = 0;
360
	git_attr_cache *cache = git_repository_attr_cache(repo);
361
	khiter_t cache_pos = git_strmap_lookup_index(cache->files, filename);
362

363 364
	if (git_strmap_valid_index(cache->files, cache_pos)) {
		*file = git_strmap_value_at(cache->files, cache_pos);
365
		return 0;
366 367
	}

368
	if (git_attr_file__new(file, 0, filename, &cache->pool) < 0)
369
		return -1;
370

371 372 373
	git_strmap_insert(cache->files, (*file)->key + 2, *file, error);
	if (error > 0)
		error = 0;
374 375 376 377

	return error;
}

378
int git_attr_cache__push_file(
379
	git_repository *repo,
380 381 382 383
	const char *base,
	const char *filename,
	git_attr_file_source source,
	git_attr_file_parser parse,
384
	void* parsedata,
385
	git_vector *stack)
386
{
387
	int error = 0;
388
	git_buf path = GIT_BUF_INIT;
389 390 391
	const char *workdir = git_repository_workdir(repo);
	const char *relfile, *content = NULL;
	git_attr_cache *cache = git_repository_attr_cache(repo);
392
	git_attr_file *file = NULL;
393
	git_blob *blob = NULL;
Vicent Marti committed
394
	git_futils_filestamp stamp;
395 396

	assert(filename && stack);
397

398 399
	/* join base and path as needed */
	if (base != NULL && git_path_root(filename) < 0) {
400 401
		if (git_buf_joinpath(&path, base, filename) < 0)
			return -1;
402 403
		filename = path.ptr;
	}
404

405 406 407
	relfile = filename;
	if (workdir && git__prefixcmp(relfile, workdir) == 0)
		relfile += strlen(workdir);
408

409
	/* check cache */
410 411
	if (load_attr_from_cache(&file, cache, source, relfile) < 0)
		return -1;
412

413
	/* if not in cache, load data, parse, and cache */
414

415
	if (source == GIT_ATTR_FILE_FROM_FILE) {
Vicent Marti committed
416
		git_futils_filestamp_set(
417
			&stamp, file ? &file->cache_data.stamp : NULL);
418

419
		error = load_attr_file(&content, &stamp, filename);
420 421 422
	} else {
		error = load_attr_blob_from_index(&content, &blob,
			repo, file ? &file->cache_data.oid : NULL, relfile);
423 424 425 426
	}

	if (error) {
		/* not finding a file is not an error for this function */
427
		if (error == GIT_ENOTFOUND) {
428 429 430 431 432 433
			giterr_clear();
			error = 0;
		}
		goto finish;
	}

434 435 436 437 438 439 440 441
	/* if we got here, we have to parse and/or reparse the file */
	if (file)
		git_attr_file__clear_rules(file);
	else {
		error = git_attr_file__new(&file, source, relfile, &cache->pool);
		if (error < 0)
			goto finish;
	}
442

443
	if (parse && (error = parse(repo, parsedata, content, file)) < 0)
444 445
		goto finish;

446
	git_strmap_insert(cache->files, file->key, file, error); //-V595
447 448 449
	if (error > 0)
		error = 0;

450 451 452 453
	/* remember "cache buster" file signature */
	if (blob)
		git_oid_cpy(&file->cache_data.oid, git_object_id((git_object *)blob));
	else
Vicent Marti committed
454
		git_futils_filestamp_set(&file->cache_data.stamp, &stamp);
455

456 457
finish:
	/* push file onto vector if we found one*/
458
	if (!error && file != NULL)
459
		error = git_vector_insert(stack, file);
460

461 462 463 464 465 466 467 468
	if (error != 0)
		git_attr_file__free(file);

	if (blob)
		git_blob_free(blob);
	else
		git__free((void *)content);

469
	git_buf_free(&path);
470

471 472 473
	return error;
}

474
#define push_attr_file(R,S,B,F) \
475
	git_attr_cache__push_file((R),(B),(F),GIT_ATTR_FILE_FROM_FILE,git_attr_file__parse_buffer,NULL,(S))
476

477 478
typedef struct {
	git_repository *repo;
479 480 481
	uint32_t flags;
	const char *workdir;
	git_index *index;
482 483 484
	git_vector *files;
} attr_walk_up_info;

485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511
int git_attr_cache__decide_sources(
	uint32_t flags, bool has_wd, bool has_index, git_attr_file_source *srcs)
{
	int count = 0;

	switch (flags & 0x03) {
	case GIT_ATTR_CHECK_FILE_THEN_INDEX:
		if (has_wd)
			srcs[count++] = GIT_ATTR_FILE_FROM_FILE;
		if (has_index)
			srcs[count++] = GIT_ATTR_FILE_FROM_INDEX;
		break;
	case GIT_ATTR_CHECK_INDEX_THEN_FILE:
		if (has_index)
			srcs[count++] = GIT_ATTR_FILE_FROM_INDEX;
		if (has_wd)
			srcs[count++] = GIT_ATTR_FILE_FROM_FILE;
		break;
	case GIT_ATTR_CHECK_INDEX_ONLY:
		if (has_index)
			srcs[count++] = GIT_ATTR_FILE_FROM_INDEX;
		break;
	}

	return count;
}

512 513
static int push_one_attr(void *ref, git_buf *path)
{
514
	int error = 0, n_src, i;
515
	attr_walk_up_info *info = (attr_walk_up_info *)ref;
516 517 518 519 520 521 522 523
	git_attr_file_source src[2];

	n_src = git_attr_cache__decide_sources(
		info->flags, info->workdir != NULL, info->index != NULL, src);

	for (i = 0; !error && i < n_src; ++i)
		error = git_attr_cache__push_file(
			info->repo, path->ptr, GIT_ATTR_FILE, src[i],
524
			git_attr_file__parse_buffer, NULL, info->files);
525 526

	return error;
527 528
}

529
static int collect_attr_files(
530 531 532 533
	git_repository *repo,
	uint32_t flags,
	const char *path,
	git_vector *files)
534
{
535
	int error;
536 537
	git_buf dir = GIT_BUF_INIT;
	const char *workdir = git_repository_workdir(repo);
538
	attr_walk_up_info info;
539

540 541 542
	if (git_attr_cache__init(repo) < 0 ||
		git_vector_init(files, 4, NULL) < 0)
		return -1;
543

544 545
	/* Resolve path in a non-bare repo */
	if (workdir != NULL)
546 547
		error = git_path_find_dir(&dir, path, workdir);
	else
548
		error = git_path_dirname_r(&dir, path);
549
	if (error < 0)
550 551 552 553 554 555 556 557 558
		goto cleanup;

	/* in precendence order highest to lowest:
	 * - $GIT_DIR/info/attributes
	 * - path components with .gitattributes
	 * - config core.attributesfile
	 * - $GIT_PREFIX/etc/gitattributes
	 */

559
	error = push_attr_file(
560
		repo, files, git_repository_path(repo), GIT_ATTR_FILE_INREPO);
561
	if (error < 0)
562 563
		goto cleanup;

564 565 566 567 568
	info.repo  = repo;
	info.flags = flags;
	info.workdir = workdir;
	if (git_repository_index__weakptr(&info.index, repo) < 0)
		giterr_clear(); /* no error even if there is no index */
569
	info.files = files;
570

571
	error = git_path_walk_up(&dir, workdir, push_one_attr, &info);
572
	if (error < 0)
573 574
		goto cleanup;

575
	if (git_repository_attr_cache(repo)->cfg_attr_file != NULL) {
576
		error = push_attr_file(
577 578 579
			repo, files, NULL, git_repository_attr_cache(repo)->cfg_attr_file);
		if (error < 0)
			goto cleanup;
580 581
	}

582 583 584 585
	if ((flags & GIT_ATTR_CHECK_NO_SYSTEM) == 0) {
		error = git_futils_find_system_file(&dir, GIT_ATTR_FILE_SYSTEM);
		if (!error)
			error = push_attr_file(repo, files, NULL, dir.ptr);
586 587
		else if (error == GIT_ENOTFOUND) {
			giterr_clear();
588
			error = 0;
589
		}
590
	}
591 592

 cleanup:
593
	if (error < 0)
594 595 596 597 598 599
		git_vector_free(files);
	git_buf_free(&dir);

	return error;
}

600
static int attr_cache__lookup_path(
601
	char **out, git_config *cfg, const char *key, const char *fallback)
602
{
603 604
	git_buf buf = GIT_BUF_INIT;
	int error;
605
	const char *cfgval = NULL;
606

607 608 609 610 611 612 613 614 615 616
	*out = NULL;

	if (!(error = git_config_get_string(&cfgval, cfg, key))) {

		/* expand leading ~/ as needed */
		if (cfgval && cfgval[0] == '~' && cfgval[1] == '/' &&
			!git_futils_find_global_file(&buf, &cfgval[2]))
			*out = git_buf_detach(&buf);
		else if (cfgval)
			*out = git__strdup(cfgval);
617

618
	} else if (error == GIT_ENOTFOUND) {
619 620
		giterr_clear();
		error = 0;
621

622 623 624 625
		if (!git_futils_find_xdg_file(&buf, fallback))
			*out = git_buf_detach(&buf);
	}

626 627
	git_buf_free(&buf);

628
	return error;
629
}
630

631
int git_attr_cache__init(git_repository *repo)
632
{
633
	int ret;
634 635
	git_attr_cache *cache = git_repository_attr_cache(repo);
	git_config *cfg;
636 637

	if (cache->initialized)
638
		return 0;
639

640
	/* cache config settings for attributes and ignores */
641
	if (git_repository_config__weakptr(&cfg, repo) < 0)
642
		return -1;
643

644 645 646
	ret = attr_cache__lookup_path(
		&cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG, GIT_ATTR_FILE_XDG);
	if (ret < 0)
647 648
		return ret;

649 650 651
	ret = attr_cache__lookup_path(
		&cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG, GIT_IGNORE_FILE_XDG);
	if (ret < 0)
652
		return ret;
653 654

	/* allocate hashtable for attribute and ignore file contents */
655
	if (cache->files == NULL) {
656
		cache->files = git_strmap_alloc();
657
		GITERR_CHECK_ALLOC(cache->files);
658 659
	}

660
	/* allocate hashtable for attribute macros */
661
	if (cache->macros == NULL) {
662
		cache->macros = git_strmap_alloc();
663
		GITERR_CHECK_ALLOC(cache->macros);
664
	}
665

666 667 668 669
	/* allocate string pool */
	if (git_pool_init(&cache->pool, 1, 0) < 0)
		return -1;

670 671 672
	cache->initialized = 1;

	/* insert default macros */
673
	return git_attr_add_macro(repo, "binary", "-diff -crlf -text");
674 675 676 677 678
}

void git_attr_cache_flush(
	git_repository *repo)
{
679
	git_attr_cache *cache;
680

681 682 683
	if (!repo)
		return;

684
	cache = git_repository_attr_cache(repo);
685

686 687
	if (cache->files != NULL) {
		git_attr_file *file;
688

689
		git_strmap_foreach_value(cache->files, file, {
690 691 692
			git_attr_file__free(file);
		});

693
		git_strmap_free(cache->files);
694 695
	}

696
	if (cache->macros != NULL) {
697 698
		git_attr_rule *rule;

699
		git_strmap_foreach_value(cache->macros, rule, {
700 701 702
			git_attr_rule__free(rule);
		});

703
		git_strmap_free(cache->macros);
704 705
	}

706 707
	git_pool_clear(&cache->pool);

708 709 710 711 712 713
	git__free(cache->cfg_attr_file);
	cache->cfg_attr_file = NULL;

	git__free(cache->cfg_excl_file);
	cache->cfg_excl_file = NULL;

714
	cache->initialized = 0;
715
}
716 717 718

int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro)
{
719
	git_strmap *macros = git_repository_attr_cache(repo)->macros;
720 721
	int error;

722
	/* TODO: generate warning log if (macro->assigns.length == 0) */
723
	if (macro->assigns.length == 0)
724
		return 0;
725

726
	git_strmap_insert(macros, macro->match.pattern, macro, error);
727
	return (error < 0) ? -1 : 0;
728
}
729 730 731 732

git_attr_rule *git_attr_cache__lookup_macro(
	git_repository *repo, const char *name)
{
733
	git_strmap *macros = git_repository_attr_cache(repo)->macros;
734 735
	khiter_t pos;

736
	pos = git_strmap_lookup_index(macros, name);
737

738
	if (!git_strmap_valid_index(macros, pos))
739 740
		return NULL;

741
	return (git_attr_rule *)git_strmap_value_at(macros, pos);
742 743
}