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

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

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

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

55 56
	*value = NULL;

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

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

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

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

80
cleanup:
81
	release_attr_files(&files);
82
	git_attr_path__free(&path);
83 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;

int git_attr_get_many(
94
	const char **values,
Linquize committed
95
	git_repository *repo,
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 116 117
	if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
		return -1;

118
	if ((error = collect_attr_files(repo, 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 164 165
	git__free(info);

	return error;
}


int git_attr_foreach(
Linquize committed
166
	git_repository *repo,
167 168
	uint32_t flags,
	const char *pathname,
169 170 171 172 173 174
	int (*callback)(const char *name, const char *value, void *payload),
	void *payload)
{
	int error;
	git_attr_path path;
	git_vector files = GIT_VECTOR_INIT;
175
	size_t i, j, k;
176 177 178
	git_attr_file *file;
	git_attr_rule *rule;
	git_attr_assignment *assign;
179
	git_strmap *seen = NULL;
180

Russell Belfer committed
181 182
	assert(repo && callback);

183 184 185
	if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0)
		return -1;

186 187
	if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0 ||
		(error = git_strmap_alloc(&seen)) < 0)
188
		goto cleanup;
189 190 191 192 193 194 195

	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 */
196
				if (git_strmap_exists(seen, assign->name))
197 198
					continue;

199
				git_strmap_insert(seen, assign->name, assign, error);
200 201
				if (error < 0)
					goto cleanup;
202

203 204
				error = callback(assign->name, assign->value, payload);
				if (error) {
205
					giterr_set_after_callback(error);
206
					goto cleanup;
207
				}
208 209 210 211 212
			}
		}
	}

cleanup:
213
	git_strmap_free(seen);
214
	release_attr_files(&files);
215
	git_attr_path__free(&path);
216 217 218 219

	return error;
}

220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 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 283 284 285 286 287
static int preload_attr_file(
	git_repository *repo,
	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(
			&preload, repo, source, base, file, git_attr_file__parse_buffer)))
		git_attr_file__free(preload);

	return error;
}

static int attr_setup(git_repository *repo)
{
	int error = 0;
	const char *workdir = git_repository_workdir(repo);
	git_index *idx = NULL;
	git_buf sys = GIT_BUF_INIT;

	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
	 */

	if (!(error = git_sysdir_find_system_file(&sys, GIT_ATTR_FILE_SYSTEM))) {
		error = preload_attr_file(
			repo, GIT_ATTR_FILE__FROM_FILE, NULL, sys.ptr);
		git_buf_free(&sys);
	}
	if (error < 0) {
		if (error == GIT_ENOTFOUND) {
			giterr_clear();
			error = 0;
		} else
			return error;
	}

	if ((error = preload_attr_file(
			repo, GIT_ATTR_FILE__FROM_FILE,
			NULL, git_repository_attr_cache(repo)->cfg_attr_file)) < 0)
		return error;

	if ((error = preload_attr_file(
			repo, GIT_ATTR_FILE__FROM_FILE,
			git_repository_path(repo), GIT_ATTR_FILE_INREPO)) < 0)
		return error;

	if (workdir != NULL &&
		(error = preload_attr_file(
			repo, GIT_ATTR_FILE__FROM_FILE, workdir, GIT_ATTR_FILE)) < 0)
		return error;

	if ((error = git_repository_index__weakptr(&idx, repo)) < 0 ||
		(error = preload_attr_file(
			repo, GIT_ATTR_FILE__FROM_INDEX, NULL, GIT_ATTR_FILE)) < 0)
		return error;

	return error;
}

288 289 290 291 292 293 294
int git_attr_add_macro(
	git_repository *repo,
	const char *name,
	const char *values)
{
	int error;
	git_attr_rule *macro = NULL;
295
	git_pool *pool;
296

Russell Belfer committed
297
	if ((error = git_attr_cache__init(repo)) < 0)
298
		return error;
299 300

	macro = git__calloc(1, sizeof(git_attr_rule));
301
	GITERR_CHECK_ALLOC(macro);
302

303 304 305
	pool = &git_repository_attr_cache(repo)->pool;

	macro->match.pattern = git_pool_strdup(pool, name);
306
	GITERR_CHECK_ALLOC(macro->match.pattern);
307 308 309 310

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

311
	error = git_attr_assignment__parse(repo, pool, &macro->assigns, &values);
312

313
	if (!error)
314 315
		error = git_attr_cache__insert_macro(repo, macro);

316
	if (error < 0)
317 318 319 320 321
		git_attr_rule__free(macro);

	return error;
}

322 323
typedef struct {
	git_repository *repo;
324 325 326
	uint32_t flags;
	const char *workdir;
	git_index *index;
327 328 329
	git_vector *files;
} attr_walk_up_info;

330
static int attr_decide_sources(
331
	uint32_t flags, bool has_wd, bool has_index, git_attr_file_source *srcs)
332 333 334 335 336 337
{
	int count = 0;

	switch (flags & 0x03) {
	case GIT_ATTR_CHECK_FILE_THEN_INDEX:
		if (has_wd)
338
			srcs[count++] = GIT_ATTR_FILE__FROM_FILE;
339
		if (has_index)
340
			srcs[count++] = GIT_ATTR_FILE__FROM_INDEX;
341 342 343
		break;
	case GIT_ATTR_CHECK_INDEX_THEN_FILE:
		if (has_index)
344
			srcs[count++] = GIT_ATTR_FILE__FROM_INDEX;
345
		if (has_wd)
346
			srcs[count++] = GIT_ATTR_FILE__FROM_FILE;
347 348 349
		break;
	case GIT_ATTR_CHECK_INDEX_ONLY:
		if (has_index)
350
			srcs[count++] = GIT_ATTR_FILE__FROM_INDEX;
351 352 353 354 355 356
		break;
	}

	return count;
}

357 358 359
static int push_attr_file(
	git_repository *repo,
	git_vector *list,
360
	git_attr_file_source source,
361 362 363 364 365 366
	const char *base,
	const char *filename)
{
	int error = 0;
	git_attr_file *file = NULL;

367
	error = git_attr_cache__get(
368
		&file, repo, source, base, filename, git_attr_file__parse_buffer);
369 370 371 372 373 374 375
	if (error < 0)
		return error;

	if (file != NULL) {
		if ((error = git_vector_insert(list, file)) < 0)
			git_attr_file__free(file);
	}
376 377 378 379

	return error;
}

380 381
static int push_one_attr(void *ref, git_buf *path)
{
382
	int error = 0, n_src, i;
383
	attr_walk_up_info *info = (attr_walk_up_info *)ref;
384
	git_attr_file_source src[2];
385

386
	n_src = attr_decide_sources(
387 388 389
		info->flags, info->workdir != NULL, info->index != NULL, src);

	for (i = 0; !error && i < n_src; ++i)
390 391
		error = push_attr_file(
			info->repo, info->files, src[i], path->ptr, GIT_ATTR_FILE);
392

393
	return error;
394 395
}

396 397 398 399 400 401 402 403 404 405 406 407
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);
}

408
static int collect_attr_files(
409 410 411 412
	git_repository *repo,
	uint32_t flags,
	const char *path,
	git_vector *files)
413
{
414
	int error;
415 416
	git_buf dir = GIT_BUF_INIT;
	const char *workdir = git_repository_workdir(repo);
417
	attr_walk_up_info info = { NULL };
418

419
	if ((error = attr_setup(repo)) < 0)
420
		return error;
421

422 423
	/* Resolve path in a non-bare repo */
	if (workdir != NULL)
424 425
		error = git_path_find_dir(&dir, path, workdir);
	else
426
		error = git_path_dirname_r(&dir, path);
427
	if (error < 0)
428 429 430 431 432 433 434 435 436
		goto cleanup;

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

437
	error = push_attr_file(
438
		repo, files, GIT_ATTR_FILE__FROM_FILE,
439
		git_repository_path(repo), GIT_ATTR_FILE_INREPO);
440
	if (error < 0)
441 442
		goto cleanup;

443 444 445 446 447
	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 */
448
	info.files = files;
449

450
	error = git_path_walk_up(&dir, workdir, push_one_attr, &info);
451
	if (error < 0)
452 453
		goto cleanup;

454
	if (git_repository_attr_cache(repo)->cfg_attr_file != NULL) {
455
		error = push_attr_file(
456
			repo, files, GIT_ATTR_FILE__FROM_FILE,
457
			NULL, git_repository_attr_cache(repo)->cfg_attr_file);
458 459
		if (error < 0)
			goto cleanup;
460 461
	}

462
	if ((flags & GIT_ATTR_CHECK_NO_SYSTEM) == 0) {
463
		error = git_sysdir_find_system_file(&dir, GIT_ATTR_FILE_SYSTEM);
464
		if (!error)
465
			error = push_attr_file(
466
				repo, files, GIT_ATTR_FILE__FROM_FILE, NULL, dir.ptr);
467 468
		else if (error == GIT_ENOTFOUND) {
			giterr_clear();
469
			error = 0;
470
		}
471
	}
472 473

 cleanup:
474
	if (error < 0)
475
		release_attr_files(files);
476 477 478 479
	git_buf_free(&dir);

	return error;
}