attrcache.c 10.8 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 "attrcache.h"

10 11 12 13 14 15 16 17 18 19 20
#include "repository.h"
#include "attr_file.h"
#include "config.h"
#include "sysdir.h"
#include "ignore.h"

GIT_INLINE(int) attr_cache_lock(git_attr_cache *cache)
{
	GIT_UNUSED(cache); /* avoid warning if threading is off */

	if (git_mutex_lock(&cache->lock) < 0) {
21
		git_error_set(GIT_ERROR_OS, "unable to get attr cache lock");
22 23 24 25 26 27 28 29 30 31 32
		return -1;
	}
	return 0;
}

GIT_INLINE(void) attr_cache_unlock(git_attr_cache *cache)
{
	GIT_UNUSED(cache); /* avoid warning if threading is off */
	git_mutex_unlock(&cache->lock);
}

33
GIT_INLINE(git_attr_file_entry *) attr_cache_lookup_entry(
34 35
	git_attr_cache *cache, const char *path)
{
36
	size_t pos = git_strmap_lookup_index(cache->files, path);
37 38 39 40 41 42 43

	if (git_strmap_valid_index(cache->files, pos))
		return git_strmap_value_at(cache->files, pos);
	else
		return NULL;
}

44 45
int git_attr_cache__alloc_file_entry(
	git_attr_file_entry **out,
46 47 48 49
	const char *base,
	const char *path,
	git_pool *pool)
{
50
	size_t baselen = 0, pathlen = strlen(path);
51 52
	size_t cachesize = sizeof(git_attr_file_entry) + pathlen + 1;
	git_attr_file_entry *ce;
53

54 55 56 57 58 59 60 61
	if (base != NULL && git_path_root(path) < 0) {
		baselen = strlen(base);
		cachesize += baselen;

		if (baselen && base[baselen - 1] != '/')
			cachesize++;
	}

62
	ce = git_pool_mallocz(pool, (uint32_t)cachesize);
63
	GIT_ERROR_CHECK_ALLOC(ce);
64

65
	if (baselen) {
66
		memcpy(ce->fullpath, base, baselen);
67 68 69 70

		if (base[baselen - 1] != '/')
			ce->fullpath[baselen++] = '/';
	}
71
	memcpy(&ce->fullpath[baselen], path, pathlen);
72

73 74 75 76 77 78 79 80
	ce->path = &ce->fullpath[baselen];
	*out = ce;

	return 0;
}

/* call with attrcache locked */
static int attr_cache_make_entry(
81
	git_attr_file_entry **out, git_repository *repo, const char *path)
82 83 84
{
	int error = 0;
	git_attr_cache *cache = git_repository_attr_cache(repo);
85
	git_attr_file_entry *entry = NULL;
86

87 88
	error = git_attr_cache__alloc_file_entry(
		&entry, git_repository_workdir(repo), path, &cache->pool);
89 90

	if (!error) {
91
		git_strmap_insert(cache->files, entry->path, entry, &error);
92 93 94 95
		if (error > 0)
			error = 0;
	}

96
	*out = entry;
97 98 99 100 101 102
	return error;
}

/* insert entry or replace existing if we raced with another thread */
static int attr_cache_upsert(git_attr_cache *cache, git_attr_file *file)
{
103
	git_attr_file_entry *entry;
104 105 106 107 108
	git_attr_file *old;

	if (attr_cache_lock(cache) < 0)
		return -1;

109
	entry = attr_cache_lookup_entry(cache, file->entry->path);
110

111
	GIT_REFCOUNT_OWN(file, entry);
112
	GIT_REFCOUNT_INC(file);
113

114 115 116 117 118
	/*
	 * Replace the existing value if another thread has
	 * created it in the meantime.
	 */
	old = git__swap(entry->file[file->source], file);
119 120 121 122 123 124 125 126 127 128 129 130 131

	if (old) {
		GIT_REFCOUNT_OWN(old, NULL);
		git_attr_file__free(old);
	}

	attr_cache_unlock(cache);
	return 0;
}

static int attr_cache_remove(git_attr_cache *cache, git_attr_file *file)
{
	int error = 0;
132
	git_attr_file_entry *entry;
133
	git_attr_file *old = NULL;
134 135 136

	if (!file)
		return 0;
137

138 139 140
	if ((error = attr_cache_lock(cache)) < 0)
		return error;

141
	if ((entry = attr_cache_lookup_entry(cache, file->entry->path)) != NULL)
142
		old = git__compare_and_swap(&entry->file[file->source], file, NULL);
143 144 145

	attr_cache_unlock(cache);

146 147 148
	if (old) {
		GIT_REFCOUNT_OWN(old, NULL);
		git_attr_file__free(old);
149
	}
150 151 152 153

	return error;
}

154 155 156 157 158
/* Look up cache entry and file.
 * - If entry is not present, create it while the cache is locked.
 * - If file is present, increment refcount before returning it, so the
 *   cache can be unlocked and it won't go away.
 */
159 160
static int attr_cache_lookup(
	git_attr_file **out_file,
161
	git_attr_file_entry **out_entry,
162
	git_repository *repo,
163
	git_attr_session *attr_session,
164
	git_attr_file_source source,
165
	const char *base,
166
	const char *filename)
167 168 169 170 171
{
	int error = 0;
	git_buf path = GIT_BUF_INIT;
	const char *wd = git_repository_workdir(repo), *relfile;
	git_attr_cache *cache = git_repository_attr_cache(repo);
172
	git_attr_file_entry *entry = NULL;
173 174 175 176
	git_attr_file *file = NULL;

	/* join base and path as needed */
	if (base != NULL && git_path_root(filename) < 0) {
177 178 179
		git_buf *p = attr_session ? &attr_session->tmp : &path;

		if (git_buf_joinpath(p, base, filename) < 0)
180
			return -1;
181 182

		filename = p->ptr;
183 184 185 186 187 188 189 190 191 192
	}

	relfile = filename;
	if (wd && !git__prefixcmp(relfile, wd))
		relfile += strlen(wd);

	/* check cache for existing entry */
	if ((error = attr_cache_lock(cache)) < 0)
		goto cleanup;

193
	entry = attr_cache_lookup_entry(cache, relfile);
194 195 196
	if (!entry)
		error = attr_cache_make_entry(&entry, repo, relfile);
	else if (entry->file[source] != NULL) {
197
		file = entry->file[source];
198 199 200 201 202
		GIT_REFCOUNT_INC(file);
	}

	attr_cache_unlock(cache);

203
cleanup:
204 205
	*out_file  = file;
	*out_entry = entry;
206

207
	git_buf_dispose(&path);
208 209 210 211 212 213
	return error;
}

int git_attr_cache__get(
	git_attr_file **out,
	git_repository *repo,
214
	git_attr_session *attr_session,
215
	git_attr_file_source source,
216 217
	const char *base,
	const char *filename,
218
	git_attr_file_parser parser)
219 220 221
{
	int error = 0;
	git_attr_cache *cache = git_repository_attr_cache(repo);
222
	git_attr_file_entry *entry = NULL;
Russell Belfer committed
223
	git_attr_file *file = NULL, *updated = NULL;
224

225
	if ((error = attr_cache_lookup(
226
			&file, &entry, repo, attr_session, source, base, filename)) < 0)
Russell Belfer committed
227
		return error;
228

Russell Belfer committed
229
	/* load file if we don't have one or if existing one is out of date */
230 231
	if (!file || (error = git_attr_file__out_of_date(repo, attr_session, file)) > 0)
		error = git_attr_file__load(&updated, repo, attr_session, entry, source, parser);
Russell Belfer committed
232 233 234 235 236 237 238 239 240

	/* if we loaded the file, insert into and/or update cache */
	if (updated) {
		if ((error = attr_cache_upsert(cache, updated)) < 0)
			git_attr_file__free(updated);
		else {
			git_attr_file__free(file); /* offset incref from lookup */
			file = updated;
		}
241 242
	}

Russell Belfer committed
243 244 245 246 247
	/* if file could not be loaded */
	if (error < 0) {
		/* remove existing entry */
		if (file) {
			attr_cache_remove(cache, file);
248
			git_attr_file__free(file); /* offset incref from lookup */
Russell Belfer committed
249 250 251 252
			file = NULL;
		}
		/* no error if file simply doesn't exist */
		if (error == GIT_ENOTFOUND) {
253
			git_error_clear();
Russell Belfer committed
254 255
			error = 0;
		}
256 257
	}

258
	*out = file;
259 260 261 262 263
	return error;
}

bool git_attr_cache__is_cached(
	git_repository *repo,
264
	git_attr_file_source source,
265 266 267 268
	const char *filename)
{
	git_attr_cache *cache = git_repository_attr_cache(repo);
	git_strmap *files;
269
	size_t pos;
270
	git_attr_file_entry *entry;
271

272
	if (!cache || !(files = cache->files))
273 274 275 276 277 278
		return false;

	pos = git_strmap_lookup_index(files, filename);
	if (!git_strmap_valid_index(files, pos))
		return false;

279
	entry = git_strmap_value_at(files, pos);
280

281
	return entry && (entry->file[source] != NULL);
282 283 284 285 286 287 288 289
}


static int attr_cache__lookup_path(
	char **out, git_config *cfg, const char *key, const char *fallback)
{
	git_buf buf = GIT_BUF_INIT;
	int error;
290
	git_config_entry *entry = NULL;
291 292 293 294 295 296 297 298 299 300

	*out = NULL;

	if ((error = git_config__lookup_entry(&entry, cfg, key, false)) < 0)
		return error;

	if (entry) {
		const char *cfgval = entry->value;

		/* expand leading ~/ as needed */
301 302 303 304
		if (cfgval && cfgval[0] == '~' && cfgval[1] == '/') {
			if (! (error = git_sysdir_expand_global_file(&buf, &cfgval[2])))
				*out = git_buf_detach(&buf);
		} else if (cfgval) {
305
			*out = git__strdup(cfgval);
306
		}
307
	}
308
	else if (!git_sysdir_find_xdg_file(&buf, fallback)) {
309
		*out = git_buf_detach(&buf);
310
	}
311

312
	git_config_entry_free(entry);
313
	git_buf_dispose(&buf);
314 315 316 317 318 319

	return error;
}

static void attr_cache__free(git_attr_cache *cache)
{
320 321
	bool unlock;

322 323 324
	if (!cache)
		return;

325
	unlock = (attr_cache_lock(cache) == 0);
326

327
	if (cache->files != NULL) {
328 329
		git_attr_file_entry *entry;
		git_attr_file *file;
330 331
		int i;

332 333 334 335 336
		git_strmap_foreach_value(cache->files, entry, {
			for (i = 0; i < GIT_ATTR_FILE_NUM_SOURCES; ++i) {
				if ((file = git__swap(entry->file[i], NULL)) != NULL) {
					GIT_REFCOUNT_OWN(file, NULL);
					git_attr_file__free(file);
337 338
				}
			}
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359
		});
		git_strmap_free(cache->files);
	}

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

		git_strmap_foreach_value(cache->macros, rule, {
			git_attr_rule__free(rule);
		});
		git_strmap_free(cache->macros);
	}

	git_pool_clear(&cache->pool);

	git__free(cache->cfg_attr_file);
	cache->cfg_attr_file = NULL;

	git__free(cache->cfg_excl_file);
	cache->cfg_excl_file = NULL;

360
	if (unlock)
361
		attr_cache_unlock(cache);
362 363 364 365 366
	git_mutex_free(&cache->lock);

	git__free(cache);
}

367
int git_attr_cache__init(git_repository *repo)
368 369 370
{
	int ret = 0;
	git_attr_cache *cache = git_repository_attr_cache(repo);
371
	git_config *cfg = NULL;
372 373 374 375 376

	if (cache)
		return 0;

	cache = git__calloc(1, sizeof(git_attr_cache));
377
	GIT_ERROR_CHECK_ALLOC(cache);
378 379 380

	/* set up lock */
	if (git_mutex_init(&cache->lock) < 0) {
381
		git_error_set(GIT_ERROR_OS, "unable to initialize lock for attr cache");
382 383 384 385
		git__free(cache);
		return -1;
	}

386 387 388
	if ((ret = git_repository_config_snapshot(&cfg, repo)) < 0)
		goto cancel;

389 390 391 392 393 394 395 396 397 398 399 400 401 402 403
	/* cache config settings for attributes and ignores */
	ret = attr_cache__lookup_path(
		&cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG, GIT_ATTR_FILE_XDG);
	if (ret < 0)
		goto cancel;

	ret = attr_cache__lookup_path(
		&cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG, GIT_IGNORE_FILE_XDG);
	if (ret < 0)
		goto cancel;

	/* allocate hashtable for attribute and ignore file contents,
	 * hashtable for attribute macros, and string pool
	 */
	if ((ret = git_strmap_alloc(&cache->files)) < 0 ||
404
		(ret = git_strmap_alloc(&cache->macros)) < 0)
405 406
		goto cancel;

407 408
	git_pool_init(&cache->pool, 1);

409 410 411 412
	cache = git__compare_and_swap(&repo->attrcache, NULL, cache);
	if (cache)
		goto cancel; /* raced with another thread, free this but no error */

413 414
	git_config_free(cfg);

415 416 417 418 419
	/* insert default macros */
	return git_attr_add_macro(repo, "binary", "-diff -crlf -text");

cancel:
	attr_cache__free(cache);
420
	git_config_free(cfg);
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444
	return ret;
}

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

	/* this could be done less expensively, but for now, we'll just free
	 * the entire attrcache and let the next use reinitialize it...
	 */
	if (repo && (cache = git__swap(repo->attrcache, NULL)) != NULL)
		attr_cache__free(cache);
}

int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro)
{
	git_attr_cache *cache = git_repository_attr_cache(repo);
	git_strmap *macros = cache->macros;
	int error;

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

445
	if (attr_cache_lock(cache) < 0) {
446
		git_error_set(GIT_ERROR_OS, "unable to get attr cache lock");
447 448
		error = -1;
	} else {
449
		git_strmap_insert(macros, macro->match.pattern, macro, &error);
450 451 452 453 454 455 456 457 458 459
		git_mutex_unlock(&cache->lock);
	}

	return (error < 0) ? -1 : 0;
}

git_attr_rule *git_attr_cache__lookup_macro(
	git_repository *repo, const char *name)
{
	git_strmap *macros = git_repository_attr_cache(repo)->macros;
460
	size_t pos;
461 462 463 464 465 466 467 468 469

	pos = git_strmap_lookup_index(macros, name);

	if (!git_strmap_valid_index(macros, pos))
		return NULL;

	return (git_attr_rule *)git_strmap_value_at(macros, pos);
}