attrcache.c 10.4 KB
Newer Older
1 2 3 4 5 6 7
#include "common.h"
#include "repository.h"
#include "attr_file.h"
#include "config.h"
#include "sysdir.h"
#include "ignore.h"

8
GIT__USE_STRMAP
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

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) {
		giterr_set(GITERR_OS, "Unable to get attr cache lock");
		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);
}

27
GIT_INLINE(git_attr_file_entry *) attr_cache_lookup_entry(
28 29 30 31 32 33 34 35 36 37
	git_attr_cache *cache, const char *path)
{
	khiter_t pos = git_strmap_lookup_index(cache->files, path);

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

38 39
int git_attr_cache__alloc_file_entry(
	git_attr_file_entry **out,
40 41 42 43
	const char *base,
	const char *path,
	git_pool *pool)
{
44
	size_t baselen = 0, pathlen = strlen(path);
45 46
	size_t cachesize = sizeof(git_attr_file_entry) + pathlen + 1;
	git_attr_file_entry *ce;
47

48 49 50 51 52 53 54 55
	if (base != NULL && git_path_root(path) < 0) {
		baselen = strlen(base);
		cachesize += baselen;

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

56
	ce = git_pool_mallocz(pool, (uint32_t)cachesize);
57 58
	GITERR_CHECK_ALLOC(ce);

59
	if (baselen) {
60
		memcpy(ce->fullpath, base, baselen);
61 62 63 64

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

67 68 69 70 71 72 73 74
	ce->path = &ce->fullpath[baselen];
	*out = ce;

	return 0;
}

/* call with attrcache locked */
static int attr_cache_make_entry(
75
	git_attr_file_entry **out, git_repository *repo, const char *path)
76 77 78
{
	int error = 0;
	git_attr_cache *cache = git_repository_attr_cache(repo);
79
	git_attr_file_entry *entry = NULL;
80

81 82
	error = git_attr_cache__alloc_file_entry(
		&entry, git_repository_workdir(repo), path, &cache->pool);
83 84

	if (!error) {
85
		git_strmap_insert(cache->files, entry->path, entry, error);
86 87 88 89
		if (error > 0)
			error = 0;
	}

90
	*out = entry;
91 92 93 94 95 96
	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)
{
97
	git_attr_file_entry *entry;
98 99 100 101 102
	git_attr_file *old;

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

103
	entry = attr_cache_lookup_entry(cache, file->entry->path);
104

105
	GIT_REFCOUNT_OWN(file, entry);
106
	GIT_REFCOUNT_INC(file);
107 108 109

	old = git__compare_and_swap(
		&entry->file[file->source], entry->file[file->source], file);
110 111 112 113 114 115 116 117 118 119 120 121 122

	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;
123
	git_attr_file_entry *entry;
124 125 126 127 128 129

	if (!file)
		return 0;
	if ((error = attr_cache_lock(cache)) < 0)
		return error;

130 131
	if ((entry = attr_cache_lookup_entry(cache, file->entry->path)) != NULL)
		file = git__compare_and_swap(&entry->file[file->source], file, NULL);
132 133 134

	attr_cache_unlock(cache);

Russell Belfer committed
135
	if (file) {
136
		GIT_REFCOUNT_OWN(file, NULL);
137
		git_attr_file__free(file);
138
	}
139 140 141 142

	return error;
}

143 144 145 146 147
/* 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.
 */
148 149
static int attr_cache_lookup(
	git_attr_file **out_file,
150
	git_attr_file_entry **out_entry,
151
	git_repository *repo,
152
	git_attr_session *attr_session,
153
	git_attr_file_source source,
154
	const char *base,
155
	const char *filename)
156 157 158 159 160
{
	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);
161
	git_attr_file_entry *entry = NULL;
162 163 164 165
	git_attr_file *file = NULL;

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

		if (git_buf_joinpath(p, base, filename) < 0)
169
			return -1;
170 171

		filename = p->ptr;
172 173 174 175 176 177 178 179 180 181
	}

	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;

182
	entry = attr_cache_lookup_entry(cache, relfile);
183 184 185
	if (!entry)
		error = attr_cache_make_entry(&entry, repo, relfile);
	else if (entry->file[source] != NULL) {
186
		file = entry->file[source];
187 188 189 190 191
		GIT_REFCOUNT_INC(file);
	}

	attr_cache_unlock(cache);

192
cleanup:
193 194
	*out_file  = file;
	*out_entry = entry;
195 196 197 198 199 200 201 202

	git_buf_free(&path);
	return error;
}

int git_attr_cache__get(
	git_attr_file **out,
	git_repository *repo,
203
	git_attr_session *attr_session,
204
	git_attr_file_source source,
205 206
	const char *base,
	const char *filename,
207
	git_attr_file_parser parser)
208 209 210
{
	int error = 0;
	git_attr_cache *cache = git_repository_attr_cache(repo);
211
	git_attr_file_entry *entry = NULL;
Russell Belfer committed
212
	git_attr_file *file = NULL, *updated = NULL;
213

214
	if ((error = attr_cache_lookup(
215
			&file, &entry, repo, attr_session, source, base, filename)) < 0)
Russell Belfer committed
216
		return error;
217

Russell Belfer committed
218
	/* load file if we don't have one or if existing one is out of date */
219 220
	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
221 222 223 224 225 226 227 228 229

	/* 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;
		}
230 231
	}

Russell Belfer committed
232 233 234 235 236
	/* if file could not be loaded */
	if (error < 0) {
		/* remove existing entry */
		if (file) {
			attr_cache_remove(cache, file);
237
			git_attr_file__free(file); /* offset incref from lookup */
Russell Belfer committed
238 239 240 241 242 243 244
			file = NULL;
		}
		/* no error if file simply doesn't exist */
		if (error == GIT_ENOTFOUND) {
			giterr_clear();
			error = 0;
		}
245 246
	}

247
	*out = file;
248 249 250 251 252
	return error;
}

bool git_attr_cache__is_cached(
	git_repository *repo,
253
	git_attr_file_source source,
254 255 256 257 258
	const char *filename)
{
	git_attr_cache *cache = git_repository_attr_cache(repo);
	git_strmap *files;
	khiter_t pos;
259
	git_attr_file_entry *entry;
260

261
	if (!cache || !(files = cache->files))
262 263 264 265 266 267
		return false;

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

268
	entry = git_strmap_value_at(files, pos);
269

270
	return entry && (entry->file[source] != NULL);
271 272 273 274 275 276 277 278
}


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;
279
	git_config_entry *entry = NULL;
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298

	*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 */
		if (cfgval && cfgval[0] == '~' && cfgval[1] == '/' &&
			!git_sysdir_find_global_file(&buf, &cfgval[2]))
			*out = git_buf_detach(&buf);
		else if (cfgval)
			*out = git__strdup(cfgval);
	}
	else if (!git_sysdir_find_xdg_file(&buf, fallback))
		*out = git_buf_detach(&buf);

299
	git_config_entry_free(entry);
300 301 302 303 304 305 306
	git_buf_free(&buf);

	return error;
}

static void attr_cache__free(git_attr_cache *cache)
{
307 308
	bool unlock;

309 310 311
	if (!cache)
		return;

312
	unlock = (git_mutex_lock(&cache->lock) == 0);
313

314
	if (cache->files != NULL) {
315 316
		git_attr_file_entry *entry;
		git_attr_file *file;
317 318
		int i;

319 320 321 322 323
		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);
324 325
				}
			}
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
		});
		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;

347 348
	if (unlock)
		git_mutex_unlock(&cache->lock);
349 350 351 352 353
	git_mutex_free(&cache->lock);

	git__free(cache);
}

354
int git_attr_cache__do_init(git_repository *repo)
355 356 357
{
	int ret = 0;
	git_attr_cache *cache = git_repository_attr_cache(repo);
358
	git_config *cfg = NULL;
359 360 361 362 363 364 365 366 367 368 369 370 371 372

	if (cache)
		return 0;

	cache = git__calloc(1, sizeof(git_attr_cache));
	GITERR_CHECK_ALLOC(cache);

	/* set up lock */
	if (git_mutex_init(&cache->lock) < 0) {
		giterr_set(GITERR_OS, "Unable to initialize lock for attr cache");
		git__free(cache);
		return -1;
	}

373 374 375
	if ((ret = git_repository_config_snapshot(&cfg, repo)) < 0)
		goto cancel;

376 377 378 379 380 381 382 383 384 385 386 387 388 389 390
	/* 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 ||
391
		(ret = git_strmap_alloc(&cache->macros)) < 0)
392 393
		goto cancel;

394 395
	git_pool_init(&cache->pool, 1);

396 397 398 399
	cache = git__compare_and_swap(&repo->attrcache, NULL, cache);
	if (cache)
		goto cancel; /* raced with another thread, free this but no error */

400 401
	git_config_free(cfg);

402 403 404 405 406
	/* insert default macros */
	return git_attr_add_macro(repo, "binary", "-diff -crlf -text");

cancel:
	attr_cache__free(cache);
407
	git_config_free(cfg);
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456
	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;

	if (git_mutex_lock(&cache->lock) < 0) {
		giterr_set(GITERR_OS, "Unable to get attr cache lock");
		error = -1;
	} else {
		git_strmap_insert(macros, macro->match.pattern, macro, error);
		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;
	khiter_t pos;

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