Blame src/attrcache.c

Packit ae9e2a
#include "common.h"
Packit ae9e2a
#include "repository.h"
Packit ae9e2a
#include "attr_file.h"
Packit ae9e2a
#include "config.h"
Packit ae9e2a
#include "sysdir.h"
Packit ae9e2a
#include "ignore.h"
Packit ae9e2a
Packit ae9e2a
GIT_INLINE(int) attr_cache_lock(git_attr_cache *cache)
Packit ae9e2a
{
Packit ae9e2a
	GIT_UNUSED(cache); /* avoid warning if threading is off */
Packit ae9e2a
Packit ae9e2a
	if (git_mutex_lock(&cache->lock) < 0) {
Packit ae9e2a
		giterr_set(GITERR_OS, "unable to get attr cache lock");
Packit ae9e2a
		return -1;
Packit ae9e2a
	}
Packit ae9e2a
	return 0;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
GIT_INLINE(void) attr_cache_unlock(git_attr_cache *cache)
Packit ae9e2a
{
Packit ae9e2a
	GIT_UNUSED(cache); /* avoid warning if threading is off */
Packit ae9e2a
	git_mutex_unlock(&cache->lock);
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
GIT_INLINE(git_attr_file_entry *) attr_cache_lookup_entry(
Packit ae9e2a
	git_attr_cache *cache, const char *path)
Packit ae9e2a
{
Packit ae9e2a
	khiter_t pos = git_strmap_lookup_index(cache->files, path);
Packit ae9e2a
Packit ae9e2a
	if (git_strmap_valid_index(cache->files, pos))
Packit ae9e2a
		return git_strmap_value_at(cache->files, pos);
Packit ae9e2a
	else
Packit ae9e2a
		return NULL;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
int git_attr_cache__alloc_file_entry(
Packit ae9e2a
	git_attr_file_entry **out,
Packit ae9e2a
	const char *base,
Packit ae9e2a
	const char *path,
Packit ae9e2a
	git_pool *pool)
Packit ae9e2a
{
Packit ae9e2a
	size_t baselen = 0, pathlen = strlen(path);
Packit ae9e2a
	size_t cachesize = sizeof(git_attr_file_entry) + pathlen + 1;
Packit ae9e2a
	git_attr_file_entry *ce;
Packit ae9e2a
Packit ae9e2a
	if (base != NULL && git_path_root(path) < 0) {
Packit ae9e2a
		baselen = strlen(base);
Packit ae9e2a
		cachesize += baselen;
Packit ae9e2a
Packit ae9e2a
		if (baselen && base[baselen - 1] != '/')
Packit ae9e2a
			cachesize++;
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	ce = git_pool_mallocz(pool, (uint32_t)cachesize);
Packit ae9e2a
	GITERR_CHECK_ALLOC(ce);
Packit ae9e2a
Packit ae9e2a
	if (baselen) {
Packit ae9e2a
		memcpy(ce->fullpath, base, baselen);
Packit ae9e2a
Packit ae9e2a
		if (base[baselen - 1] != '/')
Packit ae9e2a
			ce->fullpath[baselen++] = '/';
Packit ae9e2a
	}
Packit ae9e2a
	memcpy(&ce->fullpath[baselen], path, pathlen);
Packit ae9e2a
Packit ae9e2a
	ce->path = &ce->fullpath[baselen];
Packit ae9e2a
	*out = ce;
Packit ae9e2a
Packit ae9e2a
	return 0;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
/* call with attrcache locked */
Packit ae9e2a
static int attr_cache_make_entry(
Packit ae9e2a
	git_attr_file_entry **out, git_repository *repo, const char *path)
Packit ae9e2a
{
Packit ae9e2a
	int error = 0;
Packit ae9e2a
	git_attr_cache *cache = git_repository_attr_cache(repo);
Packit ae9e2a
	git_attr_file_entry *entry = NULL;
Packit ae9e2a
Packit ae9e2a
	error = git_attr_cache__alloc_file_entry(
Packit ae9e2a
		&entry, git_repository_workdir(repo), path, &cache->pool);
Packit ae9e2a
Packit ae9e2a
	if (!error) {
Packit ae9e2a
		git_strmap_insert(cache->files, entry->path, entry, &error);
Packit ae9e2a
		if (error > 0)
Packit ae9e2a
			error = 0;
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	*out = entry;
Packit ae9e2a
	return error;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
/* insert entry or replace existing if we raced with another thread */
Packit ae9e2a
static int attr_cache_upsert(git_attr_cache *cache, git_attr_file *file)
Packit ae9e2a
{
Packit ae9e2a
	git_attr_file_entry *entry;
Packit ae9e2a
	git_attr_file *old;
Packit ae9e2a
Packit ae9e2a
	if (attr_cache_lock(cache) < 0)
Packit ae9e2a
		return -1;
Packit ae9e2a
Packit ae9e2a
	entry = attr_cache_lookup_entry(cache, file->entry->path);
Packit ae9e2a
Packit ae9e2a
	GIT_REFCOUNT_OWN(file, entry);
Packit ae9e2a
	GIT_REFCOUNT_INC(file);
Packit ae9e2a
Packit ae9e2a
	/*
Packit ae9e2a
	 * Replace the existing value if another thread has
Packit ae9e2a
	 * created it in the meantime.
Packit ae9e2a
	 */
Packit ae9e2a
	old = git__swap(entry->file[file->source], file);
Packit ae9e2a
Packit ae9e2a
	if (old) {
Packit ae9e2a
		GIT_REFCOUNT_OWN(old, NULL);
Packit ae9e2a
		git_attr_file__free(old);
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	attr_cache_unlock(cache);
Packit ae9e2a
	return 0;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
static int attr_cache_remove(git_attr_cache *cache, git_attr_file *file)
Packit ae9e2a
{
Packit ae9e2a
	int error = 0;
Packit ae9e2a
	git_attr_file_entry *entry;
Packit ae9e2a
	git_attr_file *old = NULL;
Packit ae9e2a
Packit ae9e2a
	if (!file)
Packit ae9e2a
		return 0;
Packit ae9e2a
Packit ae9e2a
	if ((error = attr_cache_lock(cache)) < 0)
Packit ae9e2a
		return error;
Packit ae9e2a
Packit ae9e2a
	if ((entry = attr_cache_lookup_entry(cache, file->entry->path)) != NULL)
Packit ae9e2a
		old = git__compare_and_swap(&entry->file[file->source], file, NULL);
Packit ae9e2a
Packit ae9e2a
	attr_cache_unlock(cache);
Packit ae9e2a
Packit ae9e2a
	if (old) {
Packit ae9e2a
		GIT_REFCOUNT_OWN(old, NULL);
Packit ae9e2a
		git_attr_file__free(old);
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	return error;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
/* Look up cache entry and file.
Packit ae9e2a
 * - If entry is not present, create it while the cache is locked.
Packit ae9e2a
 * - If file is present, increment refcount before returning it, so the
Packit ae9e2a
 *   cache can be unlocked and it won't go away.
Packit ae9e2a
 */
Packit ae9e2a
static int attr_cache_lookup(
Packit ae9e2a
	git_attr_file **out_file,
Packit ae9e2a
	git_attr_file_entry **out_entry,
Packit ae9e2a
	git_repository *repo,
Packit ae9e2a
	git_attr_session *attr_session,
Packit ae9e2a
	git_attr_file_source source,
Packit ae9e2a
	const char *base,
Packit ae9e2a
	const char *filename)
Packit ae9e2a
{
Packit ae9e2a
	int error = 0;
Packit ae9e2a
	git_buf path = GIT_BUF_INIT;
Packit ae9e2a
	const char *wd = git_repository_workdir(repo), *relfile;
Packit ae9e2a
	git_attr_cache *cache = git_repository_attr_cache(repo);
Packit ae9e2a
	git_attr_file_entry *entry = NULL;
Packit ae9e2a
	git_attr_file *file = NULL;
Packit ae9e2a
Packit ae9e2a
	/* join base and path as needed */
Packit ae9e2a
	if (base != NULL && git_path_root(filename) < 0) {
Packit ae9e2a
		git_buf *p = attr_session ? &attr_session->tmp : &pat;;
Packit ae9e2a
Packit ae9e2a
		if (git_buf_joinpath(p, base, filename) < 0)
Packit ae9e2a
			return -1;
Packit ae9e2a
Packit ae9e2a
		filename = p->ptr;
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	relfile = filename;
Packit ae9e2a
	if (wd && !git__prefixcmp(relfile, wd))
Packit ae9e2a
		relfile += strlen(wd);
Packit ae9e2a
Packit ae9e2a
	/* check cache for existing entry */
Packit ae9e2a
	if ((error = attr_cache_lock(cache)) < 0)
Packit ae9e2a
		goto cleanup;
Packit ae9e2a
Packit ae9e2a
	entry = attr_cache_lookup_entry(cache, relfile);
Packit ae9e2a
	if (!entry)
Packit ae9e2a
		error = attr_cache_make_entry(&entry, repo, relfile);
Packit ae9e2a
	else if (entry->file[source] != NULL) {
Packit ae9e2a
		file = entry->file[source];
Packit ae9e2a
		GIT_REFCOUNT_INC(file);
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	attr_cache_unlock(cache);
Packit ae9e2a
Packit ae9e2a
cleanup:
Packit ae9e2a
	*out_file  = file;
Packit ae9e2a
	*out_entry = entry;
Packit ae9e2a
Packit ae9e2a
	git_buf_free(&path);
Packit ae9e2a
	return error;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
int git_attr_cache__get(
Packit ae9e2a
	git_attr_file **out,
Packit ae9e2a
	git_repository *repo,
Packit ae9e2a
	git_attr_session *attr_session,
Packit ae9e2a
	git_attr_file_source source,
Packit ae9e2a
	const char *base,
Packit ae9e2a
	const char *filename,
Packit ae9e2a
	git_attr_file_parser parser)
Packit ae9e2a
{
Packit ae9e2a
	int error = 0;
Packit ae9e2a
	git_attr_cache *cache = git_repository_attr_cache(repo);
Packit ae9e2a
	git_attr_file_entry *entry = NULL;
Packit ae9e2a
	git_attr_file *file = NULL, *updated = NULL;
Packit ae9e2a
Packit ae9e2a
	if ((error = attr_cache_lookup(
Packit ae9e2a
			&file, &entry, repo, attr_session, source, base, filename)) < 0)
Packit ae9e2a
		return error;
Packit ae9e2a
Packit ae9e2a
	/* load file if we don't have one or if existing one is out of date */
Packit ae9e2a
	if (!file || (error = git_attr_file__out_of_date(repo, attr_session, file)) > 0)
Packit ae9e2a
		error = git_attr_file__load(&updated, repo, attr_session, entry, source, parser);
Packit ae9e2a
Packit ae9e2a
	/* if we loaded the file, insert into and/or update cache */
Packit ae9e2a
	if (updated) {
Packit ae9e2a
		if ((error = attr_cache_upsert(cache, updated)) < 0)
Packit ae9e2a
			git_attr_file__free(updated);
Packit ae9e2a
		else {
Packit ae9e2a
			git_attr_file__free(file); /* offset incref from lookup */
Packit ae9e2a
			file = updated;
Packit ae9e2a
		}
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	/* if file could not be loaded */
Packit ae9e2a
	if (error < 0) {
Packit ae9e2a
		/* remove existing entry */
Packit ae9e2a
		if (file) {
Packit ae9e2a
			attr_cache_remove(cache, file);
Packit ae9e2a
			git_attr_file__free(file); /* offset incref from lookup */
Packit ae9e2a
			file = NULL;
Packit ae9e2a
		}
Packit ae9e2a
		/* no error if file simply doesn't exist */
Packit ae9e2a
		if (error == GIT_ENOTFOUND) {
Packit ae9e2a
			giterr_clear();
Packit ae9e2a
			error = 0;
Packit ae9e2a
		}
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	*out = file;
Packit ae9e2a
	return error;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
bool git_attr_cache__is_cached(
Packit ae9e2a
	git_repository *repo,
Packit ae9e2a
	git_attr_file_source source,
Packit ae9e2a
	const char *filename)
Packit ae9e2a
{
Packit ae9e2a
	git_attr_cache *cache = git_repository_attr_cache(repo);
Packit ae9e2a
	git_strmap *files;
Packit ae9e2a
	khiter_t pos;
Packit ae9e2a
	git_attr_file_entry *entry;
Packit ae9e2a
Packit ae9e2a
	if (!cache || !(files = cache->files))
Packit ae9e2a
		return false;
Packit ae9e2a
Packit ae9e2a
	pos = git_strmap_lookup_index(files, filename);
Packit ae9e2a
	if (!git_strmap_valid_index(files, pos))
Packit ae9e2a
		return false;
Packit ae9e2a
Packit ae9e2a
	entry = git_strmap_value_at(files, pos);
Packit ae9e2a
Packit ae9e2a
	return entry && (entry->file[source] != NULL);
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
Packit ae9e2a
static int attr_cache__lookup_path(
Packit ae9e2a
	char **out, git_config *cfg, const char *key, const char *fallback)
Packit ae9e2a
{
Packit ae9e2a
	git_buf buf = GIT_BUF_INIT;
Packit ae9e2a
	int error;
Packit ae9e2a
	git_config_entry *entry = NULL;
Packit ae9e2a
Packit ae9e2a
	*out = NULL;
Packit ae9e2a
Packit ae9e2a
	if ((error = git_config__lookup_entry(&entry, cfg, key, false)) < 0)
Packit ae9e2a
		return error;
Packit ae9e2a
Packit ae9e2a
	if (entry) {
Packit ae9e2a
		const char *cfgval = entry->value;
Packit ae9e2a
Packit ae9e2a
		/* expand leading ~/ as needed */
Packit ae9e2a
		if (cfgval && cfgval[0] == '~' && cfgval[1] == '/') {
Packit ae9e2a
			if (! (error = git_sysdir_expand_global_file(&buf, &cfgval[2])))
Packit ae9e2a
				*out = git_buf_detach(&buf;;
Packit ae9e2a
		} else if (cfgval) {
Packit ae9e2a
			*out = git__strdup(cfgval);
Packit ae9e2a
		}
Packit ae9e2a
	}
Packit ae9e2a
	else if (!git_sysdir_find_xdg_file(&buf, fallback)) {
Packit ae9e2a
		*out = git_buf_detach(&buf;;
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	git_config_entry_free(entry);
Packit ae9e2a
	git_buf_free(&buf;;
Packit ae9e2a
Packit ae9e2a
	return error;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
static void attr_cache__free(git_attr_cache *cache)
Packit ae9e2a
{
Packit ae9e2a
	bool unlock;
Packit ae9e2a
Packit ae9e2a
	if (!cache)
Packit ae9e2a
		return;
Packit ae9e2a
Packit ae9e2a
	unlock = (attr_cache_lock(cache) == 0);
Packit ae9e2a
Packit ae9e2a
	if (cache->files != NULL) {
Packit ae9e2a
		git_attr_file_entry *entry;
Packit ae9e2a
		git_attr_file *file;
Packit ae9e2a
		int i;
Packit ae9e2a
Packit ae9e2a
		git_strmap_foreach_value(cache->files, entry, {
Packit ae9e2a
			for (i = 0; i < GIT_ATTR_FILE_NUM_SOURCES; ++i) {
Packit ae9e2a
				if ((file = git__swap(entry->file[i], NULL)) != NULL) {
Packit ae9e2a
					GIT_REFCOUNT_OWN(file, NULL);
Packit ae9e2a
					git_attr_file__free(file);
Packit ae9e2a
				}
Packit ae9e2a
			}
Packit ae9e2a
		});
Packit ae9e2a
		git_strmap_free(cache->files);
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	if (cache->macros != NULL) {
Packit ae9e2a
		git_attr_rule *rule;
Packit ae9e2a
Packit ae9e2a
		git_strmap_foreach_value(cache->macros, rule, {
Packit ae9e2a
			git_attr_rule__free(rule);
Packit ae9e2a
		});
Packit ae9e2a
		git_strmap_free(cache->macros);
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	git_pool_clear(&cache->pool);
Packit ae9e2a
Packit ae9e2a
	git__free(cache->cfg_attr_file);
Packit ae9e2a
	cache->cfg_attr_file = NULL;
Packit ae9e2a
Packit ae9e2a
	git__free(cache->cfg_excl_file);
Packit ae9e2a
	cache->cfg_excl_file = NULL;
Packit ae9e2a
Packit ae9e2a
	if (unlock)
Packit ae9e2a
		attr_cache_unlock(cache);
Packit ae9e2a
	git_mutex_free(&cache->lock);
Packit ae9e2a
Packit ae9e2a
	git__free(cache);
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
int git_attr_cache__init(git_repository *repo)
Packit ae9e2a
{
Packit ae9e2a
	int ret = 0;
Packit ae9e2a
	git_attr_cache *cache = git_repository_attr_cache(repo);
Packit ae9e2a
	git_config *cfg = NULL;
Packit ae9e2a
Packit ae9e2a
	if (cache)
Packit ae9e2a
		return 0;
Packit ae9e2a
Packit ae9e2a
	cache = git__calloc(1, sizeof(git_attr_cache));
Packit ae9e2a
	GITERR_CHECK_ALLOC(cache);
Packit ae9e2a
Packit ae9e2a
	/* set up lock */
Packit ae9e2a
	if (git_mutex_init(&cache->lock) < 0) {
Packit ae9e2a
		giterr_set(GITERR_OS, "unable to initialize lock for attr cache");
Packit ae9e2a
		git__free(cache);
Packit ae9e2a
		return -1;
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	if ((ret = git_repository_config_snapshot(&cfg, repo)) < 0)
Packit ae9e2a
		goto cancel;
Packit ae9e2a
Packit ae9e2a
	/* cache config settings for attributes and ignores */
Packit ae9e2a
	ret = attr_cache__lookup_path(
Packit ae9e2a
		&cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG, GIT_ATTR_FILE_XDG);
Packit ae9e2a
	if (ret < 0)
Packit ae9e2a
		goto cancel;
Packit ae9e2a
Packit ae9e2a
	ret = attr_cache__lookup_path(
Packit ae9e2a
		&cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG, GIT_IGNORE_FILE_XDG);
Packit ae9e2a
	if (ret < 0)
Packit ae9e2a
		goto cancel;
Packit ae9e2a
Packit ae9e2a
	/* allocate hashtable for attribute and ignore file contents,
Packit ae9e2a
	 * hashtable for attribute macros, and string pool
Packit ae9e2a
	 */
Packit ae9e2a
	if ((ret = git_strmap_alloc(&cache->files)) < 0 ||
Packit ae9e2a
		(ret = git_strmap_alloc(&cache->macros)) < 0)
Packit ae9e2a
		goto cancel;
Packit ae9e2a
Packit ae9e2a
	git_pool_init(&cache->pool, 1);
Packit ae9e2a
Packit ae9e2a
	cache = git__compare_and_swap(&repo->attrcache, NULL, cache);
Packit ae9e2a
	if (cache)
Packit ae9e2a
		goto cancel; /* raced with another thread, free this but no error */
Packit ae9e2a
Packit ae9e2a
	git_config_free(cfg);
Packit ae9e2a
Packit ae9e2a
	/* insert default macros */
Packit ae9e2a
	return git_attr_add_macro(repo, "binary", "-diff -crlf -text");
Packit ae9e2a
Packit ae9e2a
cancel:
Packit ae9e2a
	attr_cache__free(cache);
Packit ae9e2a
	git_config_free(cfg);
Packit ae9e2a
	return ret;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
void git_attr_cache_flush(git_repository *repo)
Packit ae9e2a
{
Packit ae9e2a
	git_attr_cache *cache;
Packit ae9e2a
Packit ae9e2a
	/* this could be done less expensively, but for now, we'll just free
Packit ae9e2a
	 * the entire attrcache and let the next use reinitialize it...
Packit ae9e2a
	 */
Packit ae9e2a
	if (repo && (cache = git__swap(repo->attrcache, NULL)) != NULL)
Packit ae9e2a
		attr_cache__free(cache);
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro)
Packit ae9e2a
{
Packit ae9e2a
	git_attr_cache *cache = git_repository_attr_cache(repo);
Packit ae9e2a
	git_strmap *macros = cache->macros;
Packit ae9e2a
	int error;
Packit ae9e2a
Packit ae9e2a
	/* TODO: generate warning log if (macro->assigns.length == 0) */
Packit ae9e2a
	if (macro->assigns.length == 0)
Packit ae9e2a
		return 0;
Packit ae9e2a
Packit ae9e2a
	if (attr_cache_lock(cache) < 0) {
Packit ae9e2a
		giterr_set(GITERR_OS, "unable to get attr cache lock");
Packit ae9e2a
		error = -1;
Packit ae9e2a
	} else {
Packit ae9e2a
		git_strmap_insert(macros, macro->match.pattern, macro, &error);
Packit ae9e2a
		git_mutex_unlock(&cache->lock);
Packit ae9e2a
	}
Packit ae9e2a
Packit ae9e2a
	return (error < 0) ? -1 : 0;
Packit ae9e2a
}
Packit ae9e2a
Packit ae9e2a
git_attr_rule *git_attr_cache__lookup_macro(
Packit ae9e2a
	git_repository *repo, const char *name)
Packit ae9e2a
{
Packit ae9e2a
	git_strmap *macros = git_repository_attr_cache(repo)->macros;
Packit ae9e2a
	khiter_t pos;
Packit ae9e2a
Packit ae9e2a
	pos = git_strmap_lookup_index(macros, name);
Packit ae9e2a
Packit ae9e2a
	if (!git_strmap_valid_index(macros, pos))
Packit ae9e2a
		return NULL;
Packit ae9e2a
Packit ae9e2a
	return (git_attr_rule *)git_strmap_value_at(macros, pos);
Packit ae9e2a
}
Packit ae9e2a