attr.c 14.7 KB
Newer Older
1
#include "repository.h"
2 3 4 5
#include "fileops.h"
#include "config.h"
#include <ctype.h>

6
GIT__USE_STRMAP;
7

8
static int collect_attr_files(
9 10 11 12
	git_repository *repo,
	uint32_t flags,
	const char *path,
	git_vector *files);
13 14 15


int git_attr_get(
16 17 18 19 20
    git_repository *repo,
	uint32_t flags,
	const char *pathname,
	const char *name,
	const char **value)
21 22 23 24 25 26 27 28 29 30 31
{
	int error;
	git_attr_path path;
	git_vector files = GIT_VECTOR_INIT;
	unsigned int i, j;
	git_attr_file *file;
	git_attr_name attr;
	git_attr_rule *rule;

	*value = NULL;

32 33 34
	if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
		return -1;

35
	if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0)
36
		goto cleanup;
37 38 39 40 41 42 43 44 45 46 47

	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) {
			int pos = git_vector_bsearch(&rule->assigns, &attr);
			if (pos >= 0) {
				*value = ((git_attr_assignment *)git_vector_get(
							  &rule->assigns, pos))->value;
48
				goto cleanup;
49 50 51 52
			}
		}
	}

53
cleanup:
54
	git_vector_free(&files);
55
	git_attr_path__free(&path);
56 57 58 59 60 61 62 63 64 65 66

	return error;
}


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

int git_attr_get_many(
67 68 69 70 71 72
    git_repository *repo,
	uint32_t flags,
	const char *pathname,
    size_t num_attr,
	const char **names,
	const char **values)
73 74 75 76 77 78 79 80 81 82
{
	int error;
	git_attr_path path;
	git_vector files = GIT_VECTOR_INIT;
	unsigned int i, j, k;
	git_attr_file *file;
	git_attr_rule *rule;
	attr_get_many_info *info = NULL;
	size_t num_found = 0;

83
	memset((void *)values, 0, sizeof(const char *) * num_attr);
84

85 86 87
	if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
		return -1;

88
	if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0)
89
		goto cleanup;
90

91 92
	info = git__calloc(num_attr, sizeof(attr_get_many_info));
	GITERR_CHECK_ALLOC(info);
93 94 95 96 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

	git_vector_foreach(&files, i, file) {

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

			for (k = 0; k < num_attr; k++) {
				int pos;

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

				pos = git_vector_bsearch(&rule->assigns, &info[k].name);
				if (pos >= 0) {
					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);
124
	git_attr_path__free(&path);
125 126 127 128 129 130 131
	git__free(info);

	return error;
}


int git_attr_foreach(
132 133 134
    git_repository *repo,
	uint32_t flags,
	const char *pathname,
135 136 137 138 139 140 141 142 143 144
	int (*callback)(const char *name, const char *value, void *payload),
	void *payload)
{
	int error;
	git_attr_path path;
	git_vector files = GIT_VECTOR_INIT;
	unsigned int i, j, k;
	git_attr_file *file;
	git_attr_rule *rule;
	git_attr_assignment *assign;
145
	git_strmap *seen = NULL;
146

147 148 149
	if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
		return -1;

150
	if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0)
151
		goto cleanup;
152

153
	seen = git_strmap_alloc();
154
	GITERR_CHECK_ALLOC(seen);
155 156 157 158 159 160 161

	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 */
162
				if (git_strmap_exists(seen, assign->name))
163 164
					continue;

165
				git_strmap_insert(seen, assign->name, assign, error);
166
				if (error >= 0)
167
					error = callback(assign->name, assign->value, payload);
168

169
				if (error != 0)
170 171 172 173 174 175
					goto cleanup;
			}
		}
	}

cleanup:
176
	git_strmap_free(seen);
177
	git_vector_free(&files);
178
	git_attr_path__free(&path);
179 180 181 182 183

	return error;
}


184 185 186 187 188 189 190
int git_attr_add_macro(
	git_repository *repo,
	const char *name,
	const char *values)
{
	int error;
	git_attr_rule *macro = NULL;
191
	git_pool *pool;
192

193 194
	if (git_attr_cache__init(repo) < 0)
		return -1;
195 196

	macro = git__calloc(1, sizeof(git_attr_rule));
197
	GITERR_CHECK_ALLOC(macro);
198

199 200 201
	pool = &git_repository_attr_cache(repo)->pool;

	macro->match.pattern = git_pool_strdup(pool, name);
202
	GITERR_CHECK_ALLOC(macro->match.pattern);
203 204 205 206

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

207
	error = git_attr_assignment__parse(repo, pool, &macro->assigns, &values);
208

209
	if (!error)
210 211
		error = git_attr_cache__insert_macro(repo, macro);

212
	if (error < 0)
213 214 215 216 217
		git_attr_rule__free(macro);

	return error;
}

218 219
bool git_attr_cache__is_cached(
	git_repository *repo, git_attr_file_source source, const char *path)
220
{
221
	git_buf cache_key = GIT_BUF_INIT;
222
	git_strmap *files = git_repository_attr_cache(repo)->files;
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
	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;
}

238 239 240 241
static int load_attr_file(
	const char **data,
	git_attr_file_stat_sig *sig,
	const char *filename)
242 243 244
{
	int error;
	git_buf content = GIT_BUF_INIT;
245
	struct stat st;
246

247 248
	if (p_stat(filename, &st) < 0)
		return GIT_ENOTFOUND;
249

250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
	if (sig != NULL &&
		(git_time_t)st.st_mtime == sig->seconds &&
		(git_off_t)st.st_size == sig->size &&
		(unsigned int)st.st_ino == sig->ino)
		return GIT_ENOTFOUND;

	error = git_futils_readbuffer_updated(&content, filename, NULL, NULL);
	if (error < 0)
		return error;

	if (sig != NULL) {
		sig->seconds = (git_time_t)st.st_mtime;
		sig->size    = (git_off_t)st.st_size;
		sig->ino     = (unsigned int)st.st_ino;
	}

	*data = git_buf_detach(&content);

	return 0;
269
}
270

271
static int load_attr_blob_from_index(
272 273 274 275 276
	const char **content,
	git_blob **blob,
	git_repository *repo,
	const git_oid *old_oid,
	const char *relfile)
277 278 279 280 281 282
{
	int error;
	git_index *index;
	git_index_entry *entry;

	if ((error = git_repository_index__weakptr(&index, repo)) < 0 ||
283
		(error = git_index_find(index, relfile)) < 0)
284
		return error;
285

286 287
	entry = git_index_get(index, error);

288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
	if (old_oid && git_oid_cmp(old_oid, &entry->oid) == 0)
		return GIT_ENOTFOUND;

	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;
323
}
324

325
int git_attr_cache__internal_file(
326 327
	git_repository *repo,
	const char *filename,
328
	git_attr_file **file)
329
{
330
	int error = 0;
331
	git_attr_cache *cache = git_repository_attr_cache(repo);
332
	khiter_t cache_pos = git_strmap_lookup_index(cache->files, filename);
333

334 335
	if (git_strmap_valid_index(cache->files, cache_pos)) {
		*file = git_strmap_value_at(cache->files, cache_pos);
336
		return 0;
337 338
	}

339
	if (git_attr_file__new(file, 0, filename, &cache->pool) < 0)
340
		return -1;
341

342 343 344
	git_strmap_insert(cache->files, (*file)->key + 2, *file, error);
	if (error > 0)
		error = 0;
345 346 347 348

	return error;
}

349
int git_attr_cache__push_file(
350
	git_repository *repo,
351 352 353 354 355
	const char *base,
	const char *filename,
	git_attr_file_source source,
	git_attr_file_parser parse,
	git_vector *stack)
356
{
357
	int error = 0;
358
	git_buf path = GIT_BUF_INIT;
359 360 361
	const char *workdir = git_repository_workdir(repo);
	const char *relfile, *content = NULL;
	git_attr_cache *cache = git_repository_attr_cache(repo);
362
	git_attr_file *file = NULL;
363
	git_blob *blob = NULL;
364
	git_attr_file_stat_sig st;
365 366

	assert(filename && stack);
367

368 369
	/* join base and path as needed */
	if (base != NULL && git_path_root(filename) < 0) {
370 371
		if (git_buf_joinpath(&path, base, filename) < 0)
			return -1;
372 373
		filename = path.ptr;
	}
374

375 376 377
	relfile = filename;
	if (workdir && git__prefixcmp(relfile, workdir) == 0)
		relfile += strlen(workdir);
378

379
	/* check cache */
380 381
	if (load_attr_from_cache(&file, cache, source, relfile) < 0)
		return -1;
382

383
	/* if not in cache, load data, parse, and cache */
384

385 386 387 388 389
	if (source == GIT_ATTR_FILE_FROM_FILE) {
		if (file)
			memcpy(&st, &file->cache_data.st, sizeof(st));
		else
			memset(&st, 0, sizeof(st));
390

391 392 393 394
		error = load_attr_file(&content, &st, filename);
	} else {
		error = load_attr_blob_from_index(&content, &blob,
			repo, file ? &file->cache_data.oid : NULL, relfile);
395 396 397 398 399 400 401 402 403 404 405
	}

	if (error) {
		/* not finding a file is not an error for this function */
		if (error == GIT_ENOTFOUND) {
			giterr_clear();
			error = 0;
		}
		goto finish;
	}

406 407
	if (!file &&
		(error = git_attr_file__new(&file, source, relfile, &cache->pool)) < 0)
408 409
		goto finish;

410 411 412 413 414 415 416
	if (parse && (error = parse(repo, content, file)) < 0)
		goto finish;

	git_strmap_insert(cache->files, file->key, file, error);
	if (error > 0)
		error = 0;

417 418 419 420 421 422
	/* remember "cache buster" file signature */
	if (blob)
		git_oid_cpy(&file->cache_data.oid, git_object_id((git_object *)blob));
	else
		memcpy(&file->cache_data.st, &st, sizeof(st));

423 424
finish:
	/* push file onto vector if we found one*/
425
	if (!error && file != NULL)
426
		error = git_vector_insert(stack, file);
427

428 429 430 431 432 433 434 435
	if (error != 0)
		git_attr_file__free(file);

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

436
	git_buf_free(&path);
437

438 439 440
	return error;
}

441 442
#define push_attr_file(R,S,B,F) \
	git_attr_cache__push_file((R),(B),(F),GIT_ATTR_FILE_FROM_FILE,git_attr_file__parse_buffer,(S))
443

444 445
typedef struct {
	git_repository *repo;
446 447 448
	uint32_t flags;
	const char *workdir;
	git_index *index;
449 450 451
	git_vector *files;
} attr_walk_up_info;

452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478
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;
}

479 480
static int push_one_attr(void *ref, git_buf *path)
{
481
	int error = 0, n_src, i;
482
	attr_walk_up_info *info = (attr_walk_up_info *)ref;
483 484 485 486 487 488 489 490 491 492 493
	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],
			git_attr_file__parse_buffer, info->files);

	return error;
494 495
}

496
static int collect_attr_files(
497 498 499 500
	git_repository *repo,
	uint32_t flags,
	const char *path,
	git_vector *files)
501
{
502
	int error;
503 504
	git_buf dir = GIT_BUF_INIT;
	const char *workdir = git_repository_workdir(repo);
505
	attr_walk_up_info info;
506

507 508 509
	if (git_attr_cache__init(repo) < 0 ||
		git_vector_init(files, 4, NULL) < 0)
		return -1;
510

511 512
	/* Resolve path in a non-bare repo */
	if (workdir != NULL)
513 514
		error = git_path_find_dir(&dir, path, workdir);
	else
515
		error = git_path_dirname_r(&dir, path);
516
	if (error < 0)
517 518 519 520 521 522 523 524 525
		goto cleanup;

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

526
	error = push_attr_file(
527
		repo, files, git_repository_path(repo), GIT_ATTR_FILE_INREPO);
528
	if (error < 0)
529 530
		goto cleanup;

531 532 533 534 535
	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 */
536
	info.files = files;
537

538
	error = git_path_walk_up(&dir, workdir, push_one_attr, &info);
539
	if (error < 0)
540 541
		goto cleanup;

542
	if (git_repository_attr_cache(repo)->cfg_attr_file != NULL) {
543
		error = push_attr_file(
544 545 546
			repo, files, NULL, git_repository_attr_cache(repo)->cfg_attr_file);
		if (error < 0)
			goto cleanup;
547 548
	}

549 550 551 552 553 554 555
	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);
		else if (error == GIT_ENOTFOUND)
			error = 0;
	}
556 557

 cleanup:
558
	if (error < 0)
559 560 561 562 563 564 565
		git_vector_free(files);
	git_buf_free(&dir);

	return error;
}


566
int git_attr_cache__init(git_repository *repo)
567
{
568
	int ret;
569 570
	git_attr_cache *cache = git_repository_attr_cache(repo);
	git_config *cfg;
571 572

	if (cache->initialized)
573
		return 0;
574

575
	/* cache config settings for attributes and ignores */
576
	if (git_repository_config__weakptr(&cfg, repo) < 0)
577
		return -1;
578 579 580 581 582 583 584 585 586 587

	ret = git_config_get_string(cfg, GIT_ATTR_CONFIG, &cache->cfg_attr_file);
	if (ret < 0 && ret != GIT_ENOTFOUND)
		return ret;

	ret = git_config_get_string(cfg, GIT_IGNORE_CONFIG, &cache->cfg_excl_file);
	if (ret < 0 && ret != GIT_ENOTFOUND)
		return ret;

	giterr_clear();
588 589

	/* allocate hashtable for attribute and ignore file contents */
590
	if (cache->files == NULL) {
591
		cache->files = git_strmap_alloc();
592
		GITERR_CHECK_ALLOC(cache->files);
593 594
	}

595
	/* allocate hashtable for attribute macros */
596
	if (cache->macros == NULL) {
597
		cache->macros = git_strmap_alloc();
598
		GITERR_CHECK_ALLOC(cache->macros);
599
	}
600

601 602 603 604
	/* allocate string pool */
	if (git_pool_init(&cache->pool, 1, 0) < 0)
		return -1;

605 606 607
	cache->initialized = 1;

	/* insert default macros */
608
	return git_attr_add_macro(repo, "binary", "-diff -crlf -text");
609 610 611 612 613
}

void git_attr_cache_flush(
	git_repository *repo)
{
614
	git_attr_cache *cache;
615

616 617 618
	if (!repo)
		return;

619
	cache = git_repository_attr_cache(repo);
620

621 622
	if (cache->files != NULL) {
		git_attr_file *file;
623

624
		git_strmap_foreach_value(cache->files, file, {
625 626 627
			git_attr_file__free(file);
		});

628
		git_strmap_free(cache->files);
629 630
	}

631
	if (cache->macros != NULL) {
632 633
		git_attr_rule *rule;

634
		git_strmap_foreach_value(cache->macros, rule, {
635 636 637
			git_attr_rule__free(rule);
		});

638
		git_strmap_free(cache->macros);
639 640
	}

641 642 643
	git_pool_clear(&cache->pool);

	cache->initialized = 0;
644
}
645 646 647

int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro)
{
648
	git_strmap *macros = git_repository_attr_cache(repo)->macros;
649 650
	int error;

651
	/* TODO: generate warning log if (macro->assigns.length == 0) */
652
	if (macro->assigns.length == 0)
653
		return 0;
654

655
	git_strmap_insert(macros, macro->match.pattern, macro, error);
656
	return (error < 0) ? -1 : 0;
657
}
658 659 660 661

git_attr_rule *git_attr_cache__lookup_macro(
	git_repository *repo, const char *name)
{
662
	git_strmap *macros = git_repository_attr_cache(repo)->macros;
663 664
	khiter_t pos;

665
	pos = git_strmap_lookup_index(macros, name);
666

667
	if (!git_strmap_valid_index(macros, pos))
668 669
		return NULL;

670
	return (git_attr_rule *)git_strmap_value_at(macros, pos);
671 672
}