attr.c 12.7 KB
Newer Older
1 2 3 4 5 6 7
/*
 * Copyright (C) the libgit2 contributors. All rights reserved.
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */

8 9
#include "attr.h"

10
#include "repository.h"
11
#include "sysdir.h"
12
#include "config.h"
13
#include "attr_file.h"
14
#include "ignore.h"
15
#include "git2/oid.h"
16 17
#include <ctype.h>

18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
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;
}

36
static int collect_attr_files(
37
	git_repository *repo,
38
	git_attr_session *attr_session,
39 40 41
	uint32_t flags,
	const char *path,
	git_vector *files);
42

43
static void release_attr_files(git_vector *files);
44 45

int git_attr_get(
46
	const char **value,
Linquize committed
47
	git_repository *repo,
48 49
	uint32_t flags,
	const char *pathname,
50
	const char *name)
51 52 53 54
{
	int error;
	git_attr_path path;
	git_vector files = GIT_VECTOR_INIT;
55
	size_t i, j;
56 57 58
	git_attr_file *file;
	git_attr_name attr;
	git_attr_rule *rule;
59
	git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN;
60

Russell Belfer committed
61 62
	assert(value && repo && name);

63 64
	*value = NULL;

65 66 67 68
	if (git_repository_is_bare(repo))
		dir_flag = GIT_DIR_FLAG_FALSE;

	if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0)
69 70
		return -1;

71
	if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0)
72
		goto cleanup;
73

74
	memset(&attr, 0, sizeof(attr));
75 76 77 78 79 80
	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) {
81 82 83
			size_t pos;

			if (!git_vector_bsearch(&pos, &rule->assigns, &attr)) {
84 85
				*value = ((git_attr_assignment *)git_vector_get(
							  &rule->assigns, pos))->value;
86
				goto cleanup;
87 88 89 90
			}
		}
	}

91
cleanup:
92
	release_attr_files(&files);
93
	git_attr_path__free(&path);
94 95 96 97 98 99 100 101 102 103

	return error;
}


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

104
int git_attr_get_many_with_session(
105
	const char **values,
Linquize committed
106
	git_repository *repo,
107
	git_attr_session *attr_session,
108 109
	uint32_t flags,
	const char *pathname,
Linquize committed
110
	size_t num_attr,
111
	const char **names)
112 113 114 115
{
	int error;
	git_attr_path path;
	git_vector files = GIT_VECTOR_INIT;
116
	size_t i, j, k;
117 118 119 120
	git_attr_file *file;
	git_attr_rule *rule;
	attr_get_many_info *info = NULL;
	size_t num_found = 0;
121
	git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN;
122

Russell Belfer committed
123 124 125 126 127
	if (!num_attr)
		return 0;

	assert(values && repo && names);

128 129 130 131
	if (git_repository_is_bare(repo))
		dir_flag = GIT_DIR_FLAG_FALSE;

	if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0)
132 133
		return -1;

134
	if ((error = collect_attr_files(repo, attr_session, flags, pathname, &files)) < 0)
135
		goto cleanup;
136

137 138
	info = git__calloc(num_attr, sizeof(attr_get_many_info));
	GITERR_CHECK_ALLOC(info);
139 140 141 142 143 144

	git_vector_foreach(&files, i, file) {

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

			for (k = 0; k < num_attr; k++) {
145
				size_t pos;
146 147 148 149 150 151 152 153 154

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

155
				if (!git_vector_bsearch(&pos, &rule->assigns, &info[k].name)) {
156 157 158 159 160 161 162 163 164 165 166
					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;
				}
			}
		}
	}

167 168 169 170 171
	for (k = 0; k < num_attr; k++) {
		if (!info[k].found)
			values[k] = NULL;
	}

172
cleanup:
173
	release_attr_files(&files);
174
	git_attr_path__free(&path);
175 176 177 178 179
	git__free(info);

	return error;
}

180 181 182 183 184 185 186 187 188 189 190
int git_attr_get_many(
	const char **values,
	git_repository *repo,
	uint32_t flags,
	const char *pathname,
	size_t num_attr,
	const char **names)
{
	return git_attr_get_many_with_session(
		values, repo, NULL, flags, pathname, num_attr, names);
}
191 192

int git_attr_foreach(
Linquize committed
193
	git_repository *repo,
194 195
	uint32_t flags,
	const char *pathname,
196 197 198 199 200 201
	int (*callback)(const char *name, const char *value, void *payload),
	void *payload)
{
	int error;
	git_attr_path path;
	git_vector files = GIT_VECTOR_INIT;
202
	size_t i, j, k;
203 204 205
	git_attr_file *file;
	git_attr_rule *rule;
	git_attr_assignment *assign;
206
	git_strmap *seen = NULL;
207
	git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN;
208

Russell Belfer committed
209 210
	assert(repo && callback);

211 212 213 214
	if (git_repository_is_bare(repo))
		dir_flag = GIT_DIR_FLAG_FALSE;

	if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0)
215 216
		return -1;

217
	if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0 ||
218
		(error = git_strmap_alloc(&seen)) < 0)
219
		goto cleanup;
220 221 222 223 224 225 226

	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 */
227
				if (git_strmap_exists(seen, assign->name))
228 229
					continue;

230
				git_strmap_insert(seen, assign->name, assign, &error);
231 232
				if (error < 0)
					goto cleanup;
233

234 235
				error = callback(assign->name, assign->value, payload);
				if (error) {
236
					giterr_set_after_callback(error);
237
					goto cleanup;
238
				}
239 240 241 242 243
			}
		}
	}

cleanup:
244
	git_strmap_free(seen);
245
	release_attr_files(&files);
246
	git_attr_path__free(&path);
247 248 249 250

	return error;
}

251 252
static int preload_attr_file(
	git_repository *repo,
253
	git_attr_session *attr_session,
254 255 256 257 258 259 260 261 262 263
	git_attr_file_source source,
	const char *base,
	const char *file)
{
	int error;
	git_attr_file *preload = NULL;

	if (!file)
		return 0;
	if (!(error = git_attr_cache__get(
264
			&preload, repo, attr_session, source, base, file, git_attr_file__parse_buffer)))
265 266 267 268 269
		git_attr_file__free(preload);

	return error;
}

270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
static int system_attr_file(
	git_buf *out,
	git_attr_session *attr_session)
{
	int error;

	if (!attr_session) {
		error = git_sysdir_find_system_file(out, GIT_ATTR_FILE_SYSTEM);

		if (error == GIT_ENOTFOUND)
			giterr_clear();

		return error;
	}

	if (!attr_session->init_sysdir) {
		error = git_sysdir_find_system_file(&attr_session->sysdir, GIT_ATTR_FILE_SYSTEM);

		if (error == GIT_ENOTFOUND)
			giterr_clear();
		else if (error)
			return error;

		attr_session->init_sysdir = 1;
	}

	if (attr_session->sysdir.size == 0)
		return GIT_ENOTFOUND;

	/* We can safely provide a git_buf with no allocation (asize == 0) to
	 * a consumer. This allows them to treat this as a regular `git_buf`,
301
	 * but their call to `git_buf_dispose` will not attempt to free it.
302
	 */
303 304
	git_buf_attach_notowned(
		out, attr_session->sysdir.ptr, attr_session->sysdir.size);
305 306 307
	return 0;
}

308
static int attr_setup(git_repository *repo, git_attr_session *attr_session)
309 310 311 312
{
	int error = 0;
	const char *workdir = git_repository_workdir(repo);
	git_index *idx = NULL;
313
	git_buf path = GIT_BUF_INIT;
314

315
	if (attr_session && attr_session->init_setup)
316 317
		return 0;

318 319 320 321 322 323 324
	if ((error = git_attr_cache__init(repo)) < 0)
		return error;

	/* preload attribute files that could contain macros so the
	 * definitions will be available for later file parsing
	 */

325
	error = system_attr_file(&path, attr_session);
326 327

	if (error == 0)
328
		error = preload_attr_file(
329
			repo, attr_session, GIT_ATTR_FILE__FROM_FILE, NULL, path.ptr);
330 331

	if (error != GIT_ENOTFOUND)
332
		goto out;
333 334

	if ((error = preload_attr_file(
335
			repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
336
			NULL, git_repository_attr_cache(repo)->cfg_attr_file)) < 0)
337 338 339 340 341
		goto out;

	if ((error = git_repository_item_path(&path,
			repo, GIT_REPOSITORY_ITEM_INFO)) < 0)
		goto out;
342 343

	if ((error = preload_attr_file(
344
			repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
345 346
			path.ptr, GIT_ATTR_FILE_INREPO)) < 0)
		goto out;
347 348 349

	if (workdir != NULL &&
		(error = preload_attr_file(
350
			repo, attr_session, GIT_ATTR_FILE__FROM_FILE, workdir, GIT_ATTR_FILE)) < 0)
351
		goto out;
352 353 354

	if ((error = git_repository_index__weakptr(&idx, repo)) < 0 ||
		(error = preload_attr_file(
355
			repo, attr_session, GIT_ATTR_FILE__FROM_INDEX, NULL, GIT_ATTR_FILE)) < 0)
356
		goto out;
357

358
	if (attr_session)
359
		attr_session->init_setup = 1;
360

361
out:
362
	git_buf_dispose(&path);
363

364 365 366
	return error;
}

367 368 369 370 371 372 373
int git_attr_add_macro(
	git_repository *repo,
	const char *name,
	const char *values)
{
	int error;
	git_attr_rule *macro = NULL;
374
	git_pool *pool;
375

Russell Belfer committed
376
	if ((error = git_attr_cache__init(repo)) < 0)
377
		return error;
378 379

	macro = git__calloc(1, sizeof(git_attr_rule));
380
	GITERR_CHECK_ALLOC(macro);
381

382 383 384
	pool = &git_repository_attr_cache(repo)->pool;

	macro->match.pattern = git_pool_strdup(pool, name);
385
	GITERR_CHECK_ALLOC(macro->match.pattern);
386 387 388 389

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

390
	error = git_attr_assignment__parse(repo, pool, &macro->assigns, &values);
391

392
	if (!error)
393 394
		error = git_attr_cache__insert_macro(repo, macro);

395
	if (error < 0)
396 397 398 399 400
		git_attr_rule__free(macro);

	return error;
}

401 402
typedef struct {
	git_repository *repo;
403
	git_attr_session *attr_session;
404 405 406
	uint32_t flags;
	const char *workdir;
	git_index *index;
407 408 409
	git_vector *files;
} attr_walk_up_info;

410
static int attr_decide_sources(
411
	uint32_t flags, bool has_wd, bool has_index, git_attr_file_source *srcs)
412 413 414 415 416 417
{
	int count = 0;

	switch (flags & 0x03) {
	case GIT_ATTR_CHECK_FILE_THEN_INDEX:
		if (has_wd)
418
			srcs[count++] = GIT_ATTR_FILE__FROM_FILE;
419
		if (has_index)
420
			srcs[count++] = GIT_ATTR_FILE__FROM_INDEX;
421 422 423
		break;
	case GIT_ATTR_CHECK_INDEX_THEN_FILE:
		if (has_index)
424
			srcs[count++] = GIT_ATTR_FILE__FROM_INDEX;
425
		if (has_wd)
426
			srcs[count++] = GIT_ATTR_FILE__FROM_FILE;
427 428 429
		break;
	case GIT_ATTR_CHECK_INDEX_ONLY:
		if (has_index)
430
			srcs[count++] = GIT_ATTR_FILE__FROM_INDEX;
431 432 433 434 435 436
		break;
	}

	return count;
}

437 438
static int push_attr_file(
	git_repository *repo,
439
	git_attr_session *attr_session,
440
	git_vector *list,
441
	git_attr_file_source source,
442 443 444 445 446 447
	const char *base,
	const char *filename)
{
	int error = 0;
	git_attr_file *file = NULL;

448 449 450
	error = git_attr_cache__get(&file, repo, attr_session,
		source, base, filename, git_attr_file__parse_buffer);

451 452 453 454 455 456 457
	if (error < 0)
		return error;

	if (file != NULL) {
		if ((error = git_vector_insert(list, file)) < 0)
			git_attr_file__free(file);
	}
458 459 460 461

	return error;
}

462
static int push_one_attr(void *ref, const char *path)
463
{
464
	int error = 0, n_src, i;
465
	attr_walk_up_info *info = (attr_walk_up_info *)ref;
466
	git_attr_file_source src[2];
467

468
	n_src = attr_decide_sources(
469 470 471
		info->flags, info->workdir != NULL, info->index != NULL, src);

	for (i = 0; !error && i < n_src; ++i)
472 473
		error = push_attr_file(info->repo, info->attr_session,
			info->files, src[i], path, GIT_ATTR_FILE);
474

475
	return error;
476 477
}

478 479 480 481 482 483 484 485 486 487 488 489
static void release_attr_files(git_vector *files)
{
	size_t i;
	git_attr_file *file;

	git_vector_foreach(files, i, file) {
		git_attr_file__free(file);
		files->contents[i] = NULL;
	}
	git_vector_free(files);
}

490
static int collect_attr_files(
491
	git_repository *repo,
492
	git_attr_session *attr_session,
493 494 495
	uint32_t flags,
	const char *path,
	git_vector *files)
496
{
497
	int error = 0;
498
	git_buf dir = GIT_BUF_INIT, attrfile = GIT_BUF_INIT;
499
	const char *workdir = git_repository_workdir(repo);
500
	attr_walk_up_info info = { NULL };
501

502
	if ((error = attr_setup(repo, attr_session)) < 0)
503
		return error;
504

505 506
	/* Resolve path in a non-bare repo */
	if (workdir != NULL)
507
		error = git_path_find_dir(&dir, path, workdir);
508 509
	else
		error = git_path_dirname_r(&dir, path);
510
	if (error < 0)
511 512 513 514 515 516 517 518 519
		goto cleanup;

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

520 521 522 523
	error = git_repository_item_path(&attrfile, repo, GIT_REPOSITORY_ITEM_INFO);
	if (error < 0)
		goto cleanup;

524
	error = push_attr_file(
525
		repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
526
		attrfile.ptr, GIT_ATTR_FILE_INREPO);
527
	if (error < 0)
528 529
		goto cleanup;

530 531
	info.repo = repo;
	info.attr_session = attr_session;
532 533 534 535
	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

The rugged tests are fragile committed
538
	if (!strcmp(dir.ptr, "."))
539
		error = push_one_attr(&info, "");
The rugged tests are fragile committed
540
	else
541 542
		error = git_path_walk_up(&dir, workdir, push_one_attr, &info);

543
	if (error < 0)
544 545
		goto cleanup;

546
	if (git_repository_attr_cache(repo)->cfg_attr_file != NULL) {
547
		error = push_attr_file(
548
			repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
549
			NULL, git_repository_attr_cache(repo)->cfg_attr_file);
550 551
		if (error < 0)
			goto cleanup;
552 553
	}

554
	if ((flags & GIT_ATTR_CHECK_NO_SYSTEM) == 0) {
555
		error = system_attr_file(&dir, attr_session);
556

557
		if (!error)
558
			error = push_attr_file(
559 560
				repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
				NULL, dir.ptr);
561
		else if (error == GIT_ENOTFOUND)
562 563
			error = 0;
	}
564 565

 cleanup:
566
	if (error < 0)
567
		release_attr_files(files);
568 569
	git_buf_dispose(&attrfile);
	git_buf_dispose(&dir);
570 571 572

	return error;
}