attr.c 15.9 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 29
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;
}


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


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

	*value = NULL;

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

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

	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) {
66 67 68
			size_t pos;

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

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

	return error;
}


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

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

106
	memset((void *)values, 0, sizeof(const char *) * num_attr);
107

108 109 110
	if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
		return -1;

111
	if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0)
112
		goto cleanup;
113

114 115
	info = git__calloc(num_attr, sizeof(attr_get_many_info));
	GITERR_CHECK_ALLOC(info);
116 117 118 119 120 121

	git_vector_foreach(&files, i, file) {

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

			for (k = 0; k < num_attr; k++) {
122
				size_t pos;
123 124 125 126 127 128 129 130 131

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

132
				if (!git_vector_bsearch(&pos, &rule->assigns, &info[k].name)) {
133 134 135 136 137 138 139 140 141 142 143 144 145
					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;
				}
			}
		}
	}

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

	return error;
}


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

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

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

175
	seen = git_strmap_alloc();
176
	GITERR_CHECK_ALLOC(seen);
177 178 179 180 181 182 183

	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 */
184
				if (git_strmap_exists(seen, assign->name))
185 186
					continue;

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

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

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

	return error;
}


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

219 220
	if (git_attr_cache__init(repo) < 0)
		return -1;
221 222

	macro = git__calloc(1, sizeof(git_attr_rule));
223
	GITERR_CHECK_ALLOC(macro);
224

225 226 227
	pool = &git_repository_attr_cache(repo)->pool;

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

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

233
	error = git_attr_assignment__parse(repo, pool, &macro->assigns, &values);
234

235
	if (!error)
236 237
		error = git_attr_cache__insert_macro(repo, macro);

238
	if (error < 0)
239 240 241 242 243
		git_attr_rule__free(macro);

	return error;
}

244 245
bool git_attr_cache__is_cached(
	git_repository *repo, git_attr_file_source source, const char *path)
246
{
247
	git_buf cache_key = GIT_BUF_INIT;
248
	git_strmap *files = git_repository_attr_cache(repo)->files;
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
	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;
}

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

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

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

282
	error = git_futils_readbuffer(&content, filename);
283 284 285 286 287 288 289 290
	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 */
	}
291 292 293 294

	*data = git_buf_detach(&content);

	return 0;
295
}
296

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

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

313
	entry = git_index_get_byindex(index, pos);
314

315
	if (old_oid && git_oid_cmp(old_oid, &entry->oid) == 0)
316
		return GIT_ENOTFOUND;
317 318 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

	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;
350
}
351

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

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

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

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

	return error;
}

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

	assert(filename && stack);
395

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

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

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

411
	/* if not in cache, load data, parse, and cache */
412

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

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

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

432 433 434 435 436 437 438 439
	/* 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;
	}
440

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

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

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

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

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

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

467
	git_buf_free(&path);
468

469 470 471
	return error;
}

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

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

483 484 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
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;
}

510 511
static int push_one_attr(void *ref, git_buf *path)
{
512
	int error = 0, n_src, i;
513
	attr_walk_up_info *info = (attr_walk_up_info *)ref;
514 515 516 517 518 519 520 521
	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],
522
			git_attr_file__parse_buffer, NULL, info->files);
523 524

	return error;
525 526
}

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

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

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

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

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

562 563 564 565 566
	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 */
567
	info.files = files;
568

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

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

580 581 582 583
	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);
584 585
		else if (error == GIT_ENOTFOUND) {
			giterr_clear();
586
			error = 0;
587
		}
588
	}
589 590

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

	return error;
}

598 599
static int attr_cache__lookup_path(
	const char **out, git_config *cfg, const char *key, const char *fallback)
600
{
601 602
	git_buf buf = GIT_BUF_INIT;
	int error;
603

604 605
	if (!(error = git_config_get_string(out, cfg, key)))
		return 0;
606

607 608 609
	if (error == GIT_ENOTFOUND) {
		giterr_clear();
		error = 0;
610

611 612 613 614 615 616 617 618 619
		if (!git_futils_find_xdg_file(&buf, fallback))
			*out = git_buf_detach(&buf);
		else
			*out = NULL;

		git_buf_free(&buf);
	}

	return error;
620
}
621

622
int git_attr_cache__init(git_repository *repo)
623
{
624
	int ret;
625 626
	git_attr_cache *cache = git_repository_attr_cache(repo);
	git_config *cfg;
627 628

	if (cache->initialized)
629
		return 0;
630

631
	/* cache config settings for attributes and ignores */
632
	if (git_repository_config__weakptr(&cfg, repo) < 0)
633
		return -1;
634

635 636 637
	ret = attr_cache__lookup_path(
		&cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG, GIT_ATTR_FILE_XDG);
	if (ret < 0)
638 639
		return ret;

640 641 642
	ret = attr_cache__lookup_path(
		&cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG, GIT_IGNORE_FILE_XDG);
	if (ret < 0)
643
		return ret;
644 645

	/* allocate hashtable for attribute and ignore file contents */
646
	if (cache->files == NULL) {
647
		cache->files = git_strmap_alloc();
648
		GITERR_CHECK_ALLOC(cache->files);
649 650
	}

651
	/* allocate hashtable for attribute macros */
652
	if (cache->macros == NULL) {
653
		cache->macros = git_strmap_alloc();
654
		GITERR_CHECK_ALLOC(cache->macros);
655
	}
656

657 658 659 660
	/* allocate string pool */
	if (git_pool_init(&cache->pool, 1, 0) < 0)
		return -1;

661 662 663
	cache->initialized = 1;

	/* insert default macros */
664
	return git_attr_add_macro(repo, "binary", "-diff -crlf -text");
665 666 667 668 669
}

void git_attr_cache_flush(
	git_repository *repo)
{
670
	git_attr_cache *cache;
671

672 673 674
	if (!repo)
		return;

675
	cache = git_repository_attr_cache(repo);
676

677 678
	if (cache->files != NULL) {
		git_attr_file *file;
679

680
		git_strmap_foreach_value(cache->files, file, {
681 682 683
			git_attr_file__free(file);
		});

684
		git_strmap_free(cache->files);
685 686
	}

687
	if (cache->macros != NULL) {
688 689
		git_attr_rule *rule;

690
		git_strmap_foreach_value(cache->macros, rule, {
691 692 693
			git_attr_rule__free(rule);
		});

694
		git_strmap_free(cache->macros);
695 696
	}

697 698 699
	git_pool_clear(&cache->pool);

	cache->initialized = 0;
700
}
701 702 703

int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro)
{
704
	git_strmap *macros = git_repository_attr_cache(repo)->macros;
705 706
	int error;

707
	/* TODO: generate warning log if (macro->assigns.length == 0) */
708
	if (macro->assigns.length == 0)
709
		return 0;
710

711
	git_strmap_insert(macros, macro->match.pattern, macro, error);
712
	return (error < 0) ? -1 : 0;
713
}
714 715 716 717

git_attr_rule *git_attr_cache__lookup_macro(
	git_repository *repo, const char *name)
{
718
	git_strmap *macros = git_repository_attr_cache(repo)->macros;
719 720
	khiter_t pos;

721
	pos = git_strmap_lookup_index(macros, name);
722

723
	if (!git_strmap_valid_index(macros, pos))
724 725
		return NULL;

726
	return (git_attr_rule *)git_strmap_value_at(macros, pos);
727 728
}