attr.c 12 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
GIT__USE_STRMAP
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
	git_repository *repo,
32
	git_attr_session *attr_session,
33 34 35
	uint32_t flags,
	const char *path,
	git_vector *files);
36

37
static void release_attr_files(git_vector *files);
38 39

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

Russell Belfer committed
54 55
	assert(value && repo && name);

56 57
	*value = NULL;

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

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

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

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

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

	return error;
}


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

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

Russell Belfer committed
112 113 114 115 116
	if (!num_attr)
		return 0;

	assert(values && repo && names);

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

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

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

	git_vector_foreach(&files, i, file) {

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

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

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

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

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

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

	return error;
}

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

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

Russell Belfer committed
194 195
	assert(repo && callback);

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

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

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

212
				git_strmap_insert(seen, assign->name, assign, error);
213 214
				if (error < 0)
					goto cleanup;
215

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

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

	return error;
}

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

	return error;
}

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 283 284
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.
	 */
285 286
	git_buf_attach_notowned(
		out, attr_session->sysdir.ptr, attr_session->sysdir.size);
287 288 289
	return 0;
}

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

297
	if (attr_session && attr_session->init_setup)
298 299
		return 0;

300 301 302 303 304 305 306
	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
	 */

307
	error = system_attr_file(&sys, attr_session);
308 309

	if (error == 0)
310
		error = preload_attr_file(
311
			repo, attr_session, GIT_ATTR_FILE__FROM_FILE, NULL, sys.ptr);
312 313

	if (error != GIT_ENOTFOUND)
314 315 316
		return error;

	git_buf_free(&sys);
317 318

	if ((error = preload_attr_file(
319
			repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
320 321 322 323
			NULL, git_repository_attr_cache(repo)->cfg_attr_file)) < 0)
		return error;

	if ((error = preload_attr_file(
324
			repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
325 326 327 328 329
			git_repository_path(repo), GIT_ATTR_FILE_INREPO)) < 0)
		return error;

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

	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 337
		return error;

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

341 342 343
	return error;
}

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

Russell Belfer committed
353
	if ((error = git_attr_cache__init(repo)) < 0)
354
		return error;
355 356

	macro = git__calloc(1, sizeof(git_attr_rule));
357
	GITERR_CHECK_ALLOC(macro);
358

359 360 361
	pool = &git_repository_attr_cache(repo)->pool;

	macro->match.pattern = git_pool_strdup(pool, name);
362
	GITERR_CHECK_ALLOC(macro->match.pattern);
363 364 365 366

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

367
	error = git_attr_assignment__parse(repo, pool, &macro->assigns, &values);
368

369
	if (!error)
370 371
		error = git_attr_cache__insert_macro(repo, macro);

372
	if (error < 0)
373 374 375 376 377
		git_attr_rule__free(macro);

	return error;
}

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

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

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

	return count;
}

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

425 426 427
	error = git_attr_cache__get(&file, repo, attr_session,
		source, base, filename, git_attr_file__parse_buffer);

428 429 430 431 432 433 434
	if (error < 0)
		return error;

	if (file != NULL) {
		if ((error = git_vector_insert(list, file)) < 0)
			git_attr_file__free(file);
	}
435 436 437 438

	return error;
}

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

445
	n_src = attr_decide_sources(
446 447 448
		info->flags, info->workdir != NULL, info->index != NULL, src);

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

452
	return error;
453 454
}

455 456 457 458 459 460 461 462 463 464 465 466
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);
}

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

479
	if ((error = attr_setup(repo, attr_session)) < 0)
480
		return error;
481

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

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

497
	error = push_attr_file(
498
		repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
499
		git_repository_path(repo), GIT_ATTR_FILE_INREPO);
500
	if (error < 0)
501 502
		goto cleanup;

503 504
	info.repo = repo;
	info.attr_session = attr_session;
505 506 507 508
	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 */
509
	info.files = files;
510

The rugged tests are fragile committed
511
	if (!strcmp(dir.ptr, "."))
512
		error = push_one_attr(&info, "");
The rugged tests are fragile committed
513
	else
514 515
		error = git_path_walk_up(&dir, workdir, push_one_attr, &info);

516
	if (error < 0)
517 518
		goto cleanup;

519
	if (git_repository_attr_cache(repo)->cfg_attr_file != NULL) {
520
		error = push_attr_file(
521
			repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
522
			NULL, git_repository_attr_cache(repo)->cfg_attr_file);
523 524
		if (error < 0)
			goto cleanup;
525 526
	}

527
	if ((flags & GIT_ATTR_CHECK_NO_SYSTEM) == 0) {
528
		error = system_attr_file(&dir, attr_session);
529

530
		if (!error)
531
			error = push_attr_file(
532 533
				repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
				NULL, dir.ptr);
534
		else if (error == GIT_ENOTFOUND)
535 536
			error = 0;
	}
537 538

 cleanup:
539
	if (error < 0)
540
		release_attr_files(files);
541 542 543 544
	git_buf_free(&dir);

	return error;
}