attr.c 12.2 KB
Newer Older
1
#include "common.h"
2
#include "repository.h"
3
#include "sysdir.h"
4
#include "config.h"
5
#include "attr_file.h"
6
#include "ignore.h"
7
#include "git2/oid.h"
8 9
#include <ctype.h>

10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
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;
}

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

35
static void release_attr_files(git_vector *files);
36 37

int git_attr_get(
38
	const char **value,
Linquize committed
39
	git_repository *repo,
40 41
	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
	git_attr_file *file;
	git_attr_name attr;
	git_attr_rule *rule;

Russell Belfer committed
52 53
	assert(value && repo && name);

54 55
	*value = NULL;

56
	if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), GIT_DIR_FLAG_UNKNOWN) < 0)
57 58
		return -1;

59
	if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0)
60
		goto cleanup;
61

62
	memset(&attr, 0, sizeof(attr));
63 64 65 66 67 68
	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) {
69 70 71
			size_t pos;

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

79
cleanup:
80
	release_attr_files(&files);
81
	git_attr_path__free(&path);
82 83 84 85 86 87 88 89 90 91

	return error;
}


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

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

Russell Belfer committed
110 111 112 113 114
	if (!num_attr)
		return 0;

	assert(values && repo && names);

115
	if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), GIT_DIR_FLAG_UNKNOWN) < 0)
116 117
		return -1;

118
	if ((error = collect_attr_files(repo, attr_session, flags, pathname, &files)) < 0)
119
		goto cleanup;
120

121 122
	info = git__calloc(num_attr, sizeof(attr_get_many_info));
	GITERR_CHECK_ALLOC(info);
123 124 125 126 127 128

	git_vector_foreach(&files, i, file) {

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

			for (k = 0; k < num_attr; k++) {
129
				size_t pos;
130 131 132 133 134 135 136 137 138

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

139
				if (!git_vector_bsearch(&pos, &rule->assigns, &info[k].name)) {
140 141 142 143 144 145 146 147 148 149 150
					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;
				}
			}
		}
	}

151 152 153 154 155
	for (k = 0; k < num_attr; k++) {
		if (!info[k].found)
			values[k] = NULL;
	}

156
cleanup:
157
	release_attr_files(&files);
158
	git_attr_path__free(&path);
159 160 161 162 163
	git__free(info);

	return error;
}

164 165 166 167 168 169 170 171 172 173 174
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);
}
175 176

int git_attr_foreach(
Linquize committed
177
	git_repository *repo,
178 179
	uint32_t flags,
	const char *pathname,
180 181 182 183 184 185
	int (*callback)(const char *name, const char *value, void *payload),
	void *payload)
{
	int error;
	git_attr_path path;
	git_vector files = GIT_VECTOR_INIT;
186
	size_t i, j, k;
187 188 189
	git_attr_file *file;
	git_attr_rule *rule;
	git_attr_assignment *assign;
190
	git_strmap *seen = NULL;
191

Russell Belfer committed
192 193
	assert(repo && callback);

194
	if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), GIT_DIR_FLAG_UNKNOWN) < 0)
195 196
		return -1;

197
	if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0 ||
198
		(error = git_strmap_alloc(&seen)) < 0)
199
		goto cleanup;
200 201 202 203 204 205 206

	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 */
207
				if (git_strmap_exists(seen, assign->name))
208 209
					continue;

210
				git_strmap_insert(seen, assign->name, assign, &error);
211 212
				if (error < 0)
					goto cleanup;
213

214 215
				error = callback(assign->name, assign->value, payload);
				if (error) {
216
					giterr_set_after_callback(error);
217
					goto cleanup;
218
				}
219 220 221 222 223
			}
		}
	}

cleanup:
224
	git_strmap_free(seen);
225
	release_attr_files(&files);
226
	git_attr_path__free(&path);
227 228 229 230

	return error;
}

231 232
static int preload_attr_file(
	git_repository *repo,
233
	git_attr_session *attr_session,
234 235 236 237 238 239 240 241 242 243
	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(
244
			&preload, repo, attr_session, source, base, file, git_attr_file__parse_buffer)))
245 246 247 248 249
		git_attr_file__free(preload);

	return error;
}

250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282
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`,
	 * but their call to `git_buf_free` will not attempt to free it.
	 */
283 284
	git_buf_attach_notowned(
		out, attr_session->sysdir.ptr, attr_session->sysdir.size);
285 286 287
	return 0;
}

288
static int attr_setup(git_repository *repo, git_attr_session *attr_session)
289 290 291 292
{
	int error = 0;
	const char *workdir = git_repository_workdir(repo);
	git_index *idx = NULL;
293
	git_buf path = GIT_BUF_INIT;
294

295
	if (attr_session && attr_session->init_setup)
296 297
		return 0;

298 299 300 301 302 303 304
	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
	 */

305
	error = system_attr_file(&path, attr_session);
306 307

	if (error == 0)
308
		error = preload_attr_file(
309
			repo, attr_session, GIT_ATTR_FILE__FROM_FILE, NULL, path.ptr);
310 311

	if (error != GIT_ENOTFOUND)
312
		goto out;
313 314

	if ((error = preload_attr_file(
315
			repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
316
			NULL, git_repository_attr_cache(repo)->cfg_attr_file)) < 0)
317 318 319 320 321
		goto out;

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

	if ((error = preload_attr_file(
324
			repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
325 326
			path.ptr, GIT_ATTR_FILE_INREPO)) < 0)
		goto out;
327 328 329

	if (workdir != NULL &&
		(error = preload_attr_file(
330
			repo, attr_session, GIT_ATTR_FILE__FROM_FILE, workdir, GIT_ATTR_FILE)) < 0)
331
		goto out;
332 333 334

	if ((error = git_repository_index__weakptr(&idx, repo)) < 0 ||
		(error = preload_attr_file(
335
			repo, attr_session, GIT_ATTR_FILE__FROM_INDEX, NULL, GIT_ATTR_FILE)) < 0)
336
		goto out;
337

338
	if (attr_session)
339
		attr_session->init_setup = 1;
340

341 342 343
out:
	git_buf_free(&path);

344 345 346
	return error;
}

347 348 349 350 351 352 353
int git_attr_add_macro(
	git_repository *repo,
	const char *name,
	const char *values)
{
	int error;
	git_attr_rule *macro = NULL;
354
	git_pool *pool;
355

Russell Belfer committed
356
	if ((error = git_attr_cache__init(repo)) < 0)
357
		return error;
358 359

	macro = git__calloc(1, sizeof(git_attr_rule));
360
	GITERR_CHECK_ALLOC(macro);
361

362 363 364
	pool = &git_repository_attr_cache(repo)->pool;

	macro->match.pattern = git_pool_strdup(pool, name);
365
	GITERR_CHECK_ALLOC(macro->match.pattern);
366 367 368 369

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

370
	error = git_attr_assignment__parse(repo, pool, &macro->assigns, &values);
371

372
	if (!error)
373 374
		error = git_attr_cache__insert_macro(repo, macro);

375
	if (error < 0)
376 377 378 379 380
		git_attr_rule__free(macro);

	return error;
}

381 382
typedef struct {
	git_repository *repo;
383
	git_attr_session *attr_session;
384 385 386
	uint32_t flags;
	const char *workdir;
	git_index *index;
387 388 389
	git_vector *files;
} attr_walk_up_info;

390
static int attr_decide_sources(
391
	uint32_t flags, bool has_wd, bool has_index, git_attr_file_source *srcs)
392 393 394 395 396 397
{
	int count = 0;

	switch (flags & 0x03) {
	case GIT_ATTR_CHECK_FILE_THEN_INDEX:
		if (has_wd)
398
			srcs[count++] = GIT_ATTR_FILE__FROM_FILE;
399
		if (has_index)
400
			srcs[count++] = GIT_ATTR_FILE__FROM_INDEX;
401 402 403
		break;
	case GIT_ATTR_CHECK_INDEX_THEN_FILE:
		if (has_index)
404
			srcs[count++] = GIT_ATTR_FILE__FROM_INDEX;
405
		if (has_wd)
406
			srcs[count++] = GIT_ATTR_FILE__FROM_FILE;
407 408 409
		break;
	case GIT_ATTR_CHECK_INDEX_ONLY:
		if (has_index)
410
			srcs[count++] = GIT_ATTR_FILE__FROM_INDEX;
411 412 413 414 415 416
		break;
	}

	return count;
}

417 418
static int push_attr_file(
	git_repository *repo,
419
	git_attr_session *attr_session,
420
	git_vector *list,
421
	git_attr_file_source source,
422 423 424 425 426 427
	const char *base,
	const char *filename)
{
	int error = 0;
	git_attr_file *file = NULL;

428 429 430
	error = git_attr_cache__get(&file, repo, attr_session,
		source, base, filename, git_attr_file__parse_buffer);

431 432 433 434 435 436 437
	if (error < 0)
		return error;

	if (file != NULL) {
		if ((error = git_vector_insert(list, file)) < 0)
			git_attr_file__free(file);
	}
438 439 440 441

	return error;
}

442
static int push_one_attr(void *ref, const char *path)
443
{
444
	int error = 0, n_src, i;
445
	attr_walk_up_info *info = (attr_walk_up_info *)ref;
446
	git_attr_file_source src[2];
447

448
	n_src = attr_decide_sources(
449 450 451
		info->flags, info->workdir != NULL, info->index != NULL, src);

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

455
	return error;
456 457
}

458 459 460 461 462 463 464 465 466 467 468 469
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);
}

470
static int collect_attr_files(
471
	git_repository *repo,
472
	git_attr_session *attr_session,
473 474 475
	uint32_t flags,
	const char *path,
	git_vector *files)
476
{
477
	int error = 0;
478
	git_buf dir = GIT_BUF_INIT, attrfile = GIT_BUF_INIT;
479
	const char *workdir = git_repository_workdir(repo);
480
	attr_walk_up_info info = { NULL };
481

482
	if ((error = attr_setup(repo, attr_session)) < 0)
483
		return error;
484

485 486
	/* Resolve path in a non-bare repo */
	if (workdir != NULL)
487
		error = git_path_find_dir(&dir, path, workdir);
488 489
	else
		error = git_path_dirname_r(&dir, path);
490
	if (error < 0)
491 492 493 494 495 496 497 498 499
		goto cleanup;

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

500 501 502 503
	error = git_repository_item_path(&attrfile, repo, GIT_REPOSITORY_ITEM_INFO);
	if (error < 0)
		goto cleanup;

504
	error = push_attr_file(
505
		repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
506
		attrfile.ptr, GIT_ATTR_FILE_INREPO);
507
	if (error < 0)
508 509
		goto cleanup;

510 511
	info.repo = repo;
	info.attr_session = attr_session;
512 513 514 515
	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 */
516
	info.files = files;
517

The rugged tests are fragile committed
518
	if (!strcmp(dir.ptr, "."))
519
		error = push_one_attr(&info, "");
The rugged tests are fragile committed
520
	else
521 522
		error = git_path_walk_up(&dir, workdir, push_one_attr, &info);

523
	if (error < 0)
524 525
		goto cleanup;

526
	if (git_repository_attr_cache(repo)->cfg_attr_file != NULL) {
527
		error = push_attr_file(
528
			repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
529
			NULL, git_repository_attr_cache(repo)->cfg_attr_file);
530 531
		if (error < 0)
			goto cleanup;
532 533
	}

534
	if ((flags & GIT_ATTR_CHECK_NO_SYSTEM) == 0) {
535
		error = system_attr_file(&dir, attr_session);
536

537
		if (!error)
538
			error = push_attr_file(
539 540
				repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
				NULL, dir.ptr);
541
		else if (error == GIT_ENOTFOUND)
542 543
			error = 0;
	}
544 545

 cleanup:
546
	if (error < 0)
547
		release_attr_files(files);
548
	git_buf_free(&attrfile);
549 550 551 552
	git_buf_free(&dir);

	return error;
}