Blame src/attr_file.c

Packit Service 20376f
#include "common.h"
Packit Service 20376f
#include "repository.h"
Packit Service 20376f
#include "filebuf.h"
Packit Service 20376f
#include "attr_file.h"
Packit Service 20376f
#include "attrcache.h"
Packit Service 20376f
#include "git2/blob.h"
Packit Service 20376f
#include "git2/tree.h"
Packit Service 20376f
#include "index.h"
Packit Service 20376f
#include <ctype.h>
Packit Service 20376f
Packit Service 20376f
static void attr_file_free(git_attr_file *file)
Packit Service 20376f
{
Packit Service 20376f
	bool unlock = !git_mutex_lock(&file->lock);
Packit Service 20376f
	git_attr_file__clear_rules(file, false);
Packit Service 20376f
	git_pool_clear(&file->pool);
Packit Service 20376f
	if (unlock)
Packit Service 20376f
		git_mutex_unlock(&file->lock);
Packit Service 20376f
	git_mutex_free(&file->lock);
Packit Service 20376f
Packit Service 20376f
	git__memzero(file, sizeof(*file));
Packit Service 20376f
	git__free(file);
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
int git_attr_file__new(
Packit Service 20376f
	git_attr_file **out,
Packit Service 20376f
	git_attr_file_entry *entry,
Packit Service 20376f
	git_attr_file_source source)
Packit Service 20376f
{
Packit Service 20376f
	git_attr_file *attrs = git__calloc(1, sizeof(git_attr_file));
Packit Service 20376f
	GITERR_CHECK_ALLOC(attrs);
Packit Service 20376f
Packit Service 20376f
	if (git_mutex_init(&attrs->lock) < 0) {
Packit Service 20376f
		giterr_set(GITERR_OS, "failed to initialize lock");
Packit Service 20376f
		git__free(attrs);
Packit Service 20376f
		return -1;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	git_pool_init(&attrs->pool, 1);
Packit Service 20376f
	GIT_REFCOUNT_INC(attrs);
Packit Service 20376f
	attrs->entry  = entry;
Packit Service 20376f
	attrs->source = source;
Packit Service 20376f
	*out = attrs;
Packit Service 20376f
	return 0;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
int git_attr_file__clear_rules(git_attr_file *file, bool need_lock)
Packit Service 20376f
{
Packit Service 20376f
	unsigned int i;
Packit Service 20376f
	git_attr_rule *rule;
Packit Service 20376f
Packit Service 20376f
	if (need_lock && git_mutex_lock(&file->lock) < 0) {
Packit Service 20376f
		giterr_set(GITERR_OS, "failed to lock attribute file");
Packit Service 20376f
		return -1;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	git_vector_foreach(&file->rules, i, rule)
Packit Service 20376f
		git_attr_rule__free(rule);
Packit Service 20376f
	git_vector_free(&file->rules);
Packit Service 20376f
Packit Service 20376f
	if (need_lock)
Packit Service 20376f
		git_mutex_unlock(&file->lock);
Packit Service 20376f
Packit Service 20376f
	return 0;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
void git_attr_file__free(git_attr_file *file)
Packit Service 20376f
{
Packit Service 20376f
	if (!file)
Packit Service 20376f
		return;
Packit Service 20376f
	GIT_REFCOUNT_DEC(file, attr_file_free);
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
static int attr_file_oid_from_index(
Packit Service 20376f
	git_oid *oid, git_repository *repo, const char *path)
Packit Service 20376f
{
Packit Service 20376f
	int error;
Packit Service 20376f
	git_index *idx;
Packit Service 20376f
	size_t pos;
Packit Service 20376f
	const git_index_entry *entry;
Packit Service 20376f
Packit Service 20376f
	if ((error = git_repository_index__weakptr(&idx, repo)) < 0 ||
Packit Service 20376f
		(error = git_index__find_pos(&pos, idx, path, 0, 0)) < 0)
Packit Service 20376f
		return error;
Packit Service 20376f
Packit Service 20376f
	if (!(entry = git_index_get_byindex(idx, pos)))
Packit Service 20376f
		return GIT_ENOTFOUND;
Packit Service 20376f
Packit Service 20376f
	*oid = entry->id;
Packit Service 20376f
	return 0;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
int git_attr_file__load(
Packit Service 20376f
	git_attr_file **out,
Packit Service 20376f
	git_repository *repo,
Packit Service 20376f
	git_attr_session *attr_session,
Packit Service 20376f
	git_attr_file_entry *entry,
Packit Service 20376f
	git_attr_file_source source,
Packit Service 20376f
	git_attr_file_parser parser)
Packit Service 20376f
{
Packit Service 20376f
	int error = 0;
Packit Service 20376f
	git_blob *blob = NULL;
Packit Service 20376f
	git_buf content = GIT_BUF_INIT;
Packit Service 20376f
	git_attr_file *file;
Packit Service 20376f
	struct stat st;
Packit Service 20376f
	bool nonexistent = false;
Packit Service 20376f
Packit Service 20376f
	*out = NULL;
Packit Service 20376f
Packit Service 20376f
	switch (source) {
Packit Service 20376f
	case GIT_ATTR_FILE__IN_MEMORY:
Packit Service 20376f
		/* in-memory attribute file doesn't need data */
Packit Service 20376f
		break;
Packit Service 20376f
	case GIT_ATTR_FILE__FROM_INDEX: {
Packit Service 20376f
		git_oid id;
Packit Service 20376f
Packit Service 20376f
		if ((error = attr_file_oid_from_index(&id, repo, entry->path)) < 0 ||
Packit Service 20376f
			(error = git_blob_lookup(&blob, repo, &id)) < 0)
Packit Service 20376f
			return error;
Packit Service 20376f
Packit Service 20376f
		/* Do not assume that data straight from the ODB is NULL-terminated;
Packit Service 20376f
		 * copy the contents of a file to a buffer to work on */
Packit Service 20376f
		git_buf_put(&content, git_blob_rawcontent(blob), git_blob_rawsize(blob));
Packit Service 20376f
		break;
Packit Service 20376f
	}
Packit Service 20376f
	case GIT_ATTR_FILE__FROM_FILE: {
Packit Service 20376f
		int fd = -1;
Packit Service 20376f
Packit Service 20376f
		/* For open or read errors, pretend that we got ENOTFOUND. */
Packit Service 20376f
		/* TODO: issue warning when warning API is available */
Packit Service 20376f
Packit Service 20376f
		if (p_stat(entry->fullpath, &st) < 0 ||
Packit Service 20376f
			S_ISDIR(st.st_mode) ||
Packit Service 20376f
			(fd = git_futils_open_ro(entry->fullpath)) < 0 ||
Packit Service 20376f
			(error = git_futils_readbuffer_fd(&content, fd, (size_t)st.st_size)) < 0)
Packit Service 20376f
			nonexistent = true;
Packit Service 20376f
Packit Service 20376f
		if (fd >= 0)
Packit Service 20376f
			p_close(fd);
Packit Service 20376f
Packit Service 20376f
		break;
Packit Service 20376f
	}
Packit Service 20376f
	default:
Packit Service 20376f
		giterr_set(GITERR_INVALID, "unknown file source %d", source);
Packit Service 20376f
		return -1;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	if ((error = git_attr_file__new(&file, entry, source)) < 0)
Packit Service 20376f
		goto cleanup;
Packit Service 20376f
Packit Service 20376f
	/* store the key of the attr_reader; don't bother with cache
Packit Service 20376f
	 * invalidation during the same attr reader session.
Packit Service 20376f
	 */
Packit Service 20376f
	if (attr_session)
Packit Service 20376f
		file->session_key = attr_session->key;
Packit Service 20376f
Packit Service 20376f
	if (parser && (error = parser(repo, file, git_buf_cstr(&content))) < 0) {
Packit Service 20376f
		git_attr_file__free(file);
Packit Service 20376f
		goto cleanup;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	/* write cache breakers */
Packit Service 20376f
	if (nonexistent)
Packit Service 20376f
		file->nonexistent = 1;
Packit Service 20376f
	else if (source == GIT_ATTR_FILE__FROM_INDEX)
Packit Service 20376f
		git_oid_cpy(&file->cache_data.oid, git_blob_id(blob));
Packit Service 20376f
	else if (source == GIT_ATTR_FILE__FROM_FILE)
Packit Service 20376f
		git_futils_filestamp_set_from_stat(&file->cache_data.stamp, &st);
Packit Service 20376f
	/* else always cacheable */
Packit Service 20376f
Packit Service 20376f
	*out = file;
Packit Service 20376f
Packit Service 20376f
cleanup:
Packit Service 20376f
	git_blob_free(blob);
Packit Service 20376f
	git_buf_free(&content);
Packit Service 20376f
Packit Service 20376f
	return error;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
int git_attr_file__out_of_date(
Packit Service 20376f
	git_repository *repo,
Packit Service 20376f
	git_attr_session *attr_session,
Packit Service 20376f
	git_attr_file *file)
Packit Service 20376f
{
Packit Service 20376f
	if (!file)
Packit Service 20376f
		return 1;
Packit Service 20376f
Packit Service 20376f
	/* we are never out of date if we just created this data in the same
Packit Service 20376f
	 * attr_session; otherwise, nonexistent files must be invalidated
Packit Service 20376f
	 */
Packit Service 20376f
	if (attr_session && attr_session->key == file->session_key)
Packit Service 20376f
		return 0;
Packit Service 20376f
	else if (file->nonexistent)
Packit Service 20376f
		return 1;
Packit Service 20376f
Packit Service 20376f
	switch (file->source) {
Packit Service 20376f
	case GIT_ATTR_FILE__IN_MEMORY:
Packit Service 20376f
		return 0;
Packit Service 20376f
Packit Service 20376f
	case GIT_ATTR_FILE__FROM_FILE:
Packit Service 20376f
		return git_futils_filestamp_check(
Packit Service 20376f
			&file->cache_data.stamp, file->entry->fullpath);
Packit Service 20376f
Packit Service 20376f
	case GIT_ATTR_FILE__FROM_INDEX: {
Packit Service 20376f
		int error;
Packit Service 20376f
		git_oid id;
Packit Service 20376f
Packit Service 20376f
		if ((error = attr_file_oid_from_index(
Packit Service 20376f
				&id, repo, file->entry->path)) < 0)
Packit Service 20376f
			return error;
Packit Service 20376f
Packit Service 20376f
		return (git_oid__cmp(&file->cache_data.oid, &id) != 0);
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	default:
Packit Service 20376f
		giterr_set(GITERR_INVALID, "invalid file type %d", file->source);
Packit Service 20376f
		return -1;
Packit Service 20376f
	}
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
static int sort_by_hash_and_name(const void *a_raw, const void *b_raw);
Packit Service 20376f
static void git_attr_rule__clear(git_attr_rule *rule);
Packit Service 20376f
static bool parse_optimized_patterns(
Packit Service 20376f
	git_attr_fnmatch *spec,
Packit Service 20376f
	git_pool *pool,
Packit Service 20376f
	const char *pattern);
Packit Service 20376f
Packit Service 20376f
int git_attr_file__parse_buffer(
Packit Service 20376f
	git_repository *repo, git_attr_file *attrs, const char *data)
Packit Service 20376f
{
Packit Service 20376f
	int error = 0;
Packit Service 20376f
	const char *scan = data, *context = NULL;
Packit Service 20376f
	git_attr_rule *rule = NULL;
Packit Service 20376f
Packit Service 20376f
	/* if subdir file path, convert context for file paths */
Packit Service 20376f
	if (attrs->entry &&
Packit Service 20376f
		git_path_root(attrs->entry->path) < 0 &&
Packit Service 20376f
		!git__suffixcmp(attrs->entry->path, "/" GIT_ATTR_FILE))
Packit Service 20376f
		context = attrs->entry->path;
Packit Service 20376f
Packit Service 20376f
	if (git_mutex_lock(&attrs->lock) < 0) {
Packit Service 20376f
		giterr_set(GITERR_OS, "failed to lock attribute file");
Packit Service 20376f
		return -1;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	while (!error && *scan) {
Packit Service 20376f
		/* allocate rule if needed */
Packit Service 20376f
		if (!rule && !(rule = git__calloc(1, sizeof(*rule)))) {
Packit Service 20376f
			error = -1;
Packit Service 20376f
			break;
Packit Service 20376f
		}
Packit Service 20376f
Packit Service 20376f
		rule->match.flags =
Packit Service 20376f
			GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO;
Packit Service 20376f
Packit Service 20376f
		/* parse the next "pattern attr attr attr" line */
Packit Service 20376f
		if (!(error = git_attr_fnmatch__parse(
Packit Service 20376f
				&rule->match, &attrs->pool, context, &scan)) &&
Packit Service 20376f
			!(error = git_attr_assignment__parse(
Packit Service 20376f
				repo, &attrs->pool, &rule->assigns, &scan)))
Packit Service 20376f
		{
Packit Service 20376f
			if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO)
Packit Service 20376f
				/* TODO: warning if macro found in file below repo root */
Packit Service 20376f
				error = git_attr_cache__insert_macro(repo, rule);
Packit Service 20376f
			else
Packit Service 20376f
				error = git_vector_insert(&attrs->rules, rule);
Packit Service 20376f
		}
Packit Service 20376f
Packit Service 20376f
		/* if the rule wasn't a pattern, on to the next */
Packit Service 20376f
		if (error < 0) {
Packit Service 20376f
			git_attr_rule__clear(rule); /* reset rule contents */
Packit Service 20376f
			if (error == GIT_ENOTFOUND)
Packit Service 20376f
				error = 0;
Packit Service 20376f
		} else {
Packit Service 20376f
			rule = NULL; /* vector now "owns" the rule */
Packit Service 20376f
		}
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	git_mutex_unlock(&attrs->lock);
Packit Service 20376f
	git_attr_rule__free(rule);
Packit Service 20376f
Packit Service 20376f
	return error;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
uint32_t git_attr_file__name_hash(const char *name)
Packit Service 20376f
{
Packit Service 20376f
	uint32_t h = 5381;
Packit Service 20376f
	int c;
Packit Service 20376f
	assert(name);
Packit Service 20376f
	while ((c = (int)*name++) != 0)
Packit Service 20376f
		h = ((h << 5) + h) + c;
Packit Service 20376f
	return h;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
int git_attr_file__lookup_one(
Packit Service 20376f
	git_attr_file *file,
Packit Service 20376f
	git_attr_path *path,
Packit Service 20376f
	const char *attr,
Packit Service 20376f
	const char **value)
Packit Service 20376f
{
Packit Service 20376f
	size_t i;
Packit Service 20376f
	git_attr_name name;
Packit Service 20376f
	git_attr_rule *rule;
Packit Service 20376f
Packit Service 20376f
	*value = NULL;
Packit Service 20376f
Packit Service 20376f
	name.name = attr;
Packit Service 20376f
	name.name_hash = git_attr_file__name_hash(attr);
Packit Service 20376f
Packit Service 20376f
	git_attr_file__foreach_matching_rule(file, path, i, rule) {
Packit Service 20376f
		size_t pos;
Packit Service 20376f
Packit Service 20376f
		if (!git_vector_bsearch(&pos, &rule->assigns, &name)) {
Packit Service 20376f
			*value = ((git_attr_assignment *)
Packit Service 20376f
					  git_vector_get(&rule->assigns, pos))->value;
Packit Service 20376f
			break;
Packit Service 20376f
		}
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	return 0;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
int git_attr_file__load_standalone(git_attr_file **out, const char *path)
Packit Service 20376f
{
Packit Service 20376f
	int error;
Packit Service 20376f
	git_attr_file *file;
Packit Service 20376f
	git_buf content = GIT_BUF_INIT;
Packit Service 20376f
Packit Service 20376f
	error = git_attr_file__new(&file, NULL, GIT_ATTR_FILE__FROM_FILE);
Packit Service 20376f
	if (error < 0)
Packit Service 20376f
		return error;
Packit Service 20376f
Packit Service 20376f
	error = git_attr_cache__alloc_file_entry(
Packit Service 20376f
		&file->entry, NULL, path, &file->pool);
Packit Service 20376f
	if (error < 0) {
Packit Service 20376f
		git_attr_file__free(file);
Packit Service 20376f
		return error;
Packit Service 20376f
	}
Packit Service 20376f
	/* because the cache entry is allocated from the file's own pool, we
Packit Service 20376f
	 * don't have to free it - freeing file+pool will free cache entry, too.
Packit Service 20376f
	 */
Packit Service 20376f
Packit Service 20376f
	if (!(error = git_futils_readbuffer(&content, path))) {
Packit Service 20376f
		error = git_attr_file__parse_buffer(NULL, file, content.ptr);
Packit Service 20376f
		git_buf_free(&content);
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	if (error < 0)
Packit Service 20376f
		git_attr_file__free(file);
Packit Service 20376f
	else
Packit Service 20376f
		*out = file;
Packit Service 20376f
Packit Service 20376f
	return error;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
bool git_attr_fnmatch__match(
Packit Service 20376f
	git_attr_fnmatch *match,
Packit Service 20376f
	git_attr_path *path)
Packit Service 20376f
{
Packit Service 20376f
	const char *relpath = path->path;
Packit Service 20376f
	const char *filename;
Packit Service 20376f
	int flags = 0;
Packit Service 20376f
Packit Service 20376f
	/*
Packit Service 20376f
	 * If the rule was generated in a subdirectory, we must only
Packit Service 20376f
	 * use it for paths inside that directory. We can thus return
Packit Service 20376f
	 * a non-match if the prefixes don't match.
Packit Service 20376f
	 */
Packit Service 20376f
	if (match->containing_dir) {
Packit Service 20376f
		if (match->flags & GIT_ATTR_FNMATCH_ICASE) {
Packit Service 20376f
			if (git__strncasecmp(path->path, match->containing_dir, match->containing_dir_length))
Packit Service 20376f
				return 0;
Packit Service 20376f
		} else {
Packit Service 20376f
			if (git__prefixcmp(path->path, match->containing_dir))
Packit Service 20376f
				return 0;
Packit Service 20376f
		}
Packit Service 20376f
Packit Service 20376f
		relpath += match->containing_dir_length;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	if (match->flags & GIT_ATTR_FNMATCH_ICASE)
Packit Service 20376f
		flags |= FNM_CASEFOLD;
Packit Service 20376f
	if (match->flags & GIT_ATTR_FNMATCH_LEADINGDIR)
Packit Service 20376f
		flags |= FNM_LEADING_DIR;
Packit Service 20376f
Packit Service 20376f
	if (match->flags & GIT_ATTR_FNMATCH_FULLPATH) {
Packit Service 20376f
		filename = relpath;
Packit Service 20376f
		flags |= FNM_PATHNAME;
Packit Service 20376f
	} else {
Packit Service 20376f
		filename = path->basename;
Packit Service 20376f
Packit Service 20376f
		if (path->is_dir)
Packit Service 20376f
			flags |= FNM_LEADING_DIR;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir) {
Packit Service 20376f
		bool samename;
Packit Service 20376f
Packit Service 20376f
		/*
Packit Service 20376f
		 * for attribute checks or checks at the root of this match's
Packit Service 20376f
		 * containing_dir (or root of the repository if no containing_dir),
Packit Service 20376f
		 * do not match.
Packit Service 20376f
		 */
Packit Service 20376f
		if (!(match->flags & GIT_ATTR_FNMATCH_IGNORE) ||
Packit Service 20376f
			path->basename == relpath)
Packit Service 20376f
			return false;
Packit Service 20376f
Packit Service 20376f
		flags |= FNM_LEADING_DIR;
Packit Service 20376f
Packit Service 20376f
		/* fail match if this is a file with same name as ignored folder */
Packit Service 20376f
		samename = (match->flags & GIT_ATTR_FNMATCH_ICASE) ?
Packit Service 20376f
			!strcasecmp(match->pattern, relpath) :
Packit Service 20376f
			!strcmp(match->pattern, relpath);
Packit Service 20376f
Packit Service 20376f
		if (samename)
Packit Service 20376f
			return false;
Packit Service 20376f
Packit Service 20376f
		return (p_fnmatch(match->pattern, relpath, flags) != FNM_NOMATCH);
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	/* if path is a directory prefix of a negated pattern, then match */
Packit Service 20376f
	if ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) && path->is_dir) {
Packit Service 20376f
		size_t pathlen = strlen(relpath);
Packit Service 20376f
		bool prefixed = (pathlen <= match->length) &&
Packit Service 20376f
			((match->flags & GIT_ATTR_FNMATCH_ICASE) ?
Packit Service 20376f
			!strncasecmp(match->pattern, relpath, pathlen) :
Packit Service 20376f
			!strncmp(match->pattern, relpath, pathlen));
Packit Service 20376f
Packit Service 20376f
		if (prefixed && git_path_at_end_of_segment(&match->pattern[pathlen]))
Packit Service 20376f
			return true;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	return (p_fnmatch(match->pattern, filename, flags) != FNM_NOMATCH);
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
bool git_attr_rule__match(
Packit Service 20376f
	git_attr_rule *rule,
Packit Service 20376f
	git_attr_path *path)
Packit Service 20376f
{
Packit Service 20376f
	bool matched = git_attr_fnmatch__match(&rule->match, path);
Packit Service 20376f
Packit Service 20376f
	if (rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE)
Packit Service 20376f
		matched = !matched;
Packit Service 20376f
Packit Service 20376f
	return matched;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
git_attr_assignment *git_attr_rule__lookup_assignment(
Packit Service 20376f
	git_attr_rule *rule, const char *name)
Packit Service 20376f
{
Packit Service 20376f
	size_t pos;
Packit Service 20376f
	git_attr_name key;
Packit Service 20376f
	key.name = name;
Packit Service 20376f
	key.name_hash = git_attr_file__name_hash(name);
Packit Service 20376f
Packit Service 20376f
	if (git_vector_bsearch(&pos, &rule->assigns, &key))
Packit Service 20376f
		return NULL;
Packit Service 20376f
Packit Service 20376f
	return git_vector_get(&rule->assigns, pos);
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
int git_attr_path__init(
Packit Service 20376f
	git_attr_path *info, const char *path, const char *base, git_dir_flag dir_flag)
Packit Service 20376f
{
Packit Service 20376f
	ssize_t root;
Packit Service 20376f
Packit Service 20376f
	/* build full path as best we can */
Packit Service 20376f
	git_buf_init(&info->full, 0);
Packit Service 20376f
Packit Service 20376f
	if (git_path_join_unrooted(&info->full, path, base, &root) < 0)
Packit Service 20376f
		return -1;
Packit Service 20376f
Packit Service 20376f
	info->path = info->full.ptr + root;
Packit Service 20376f
Packit Service 20376f
	/* remove trailing slashes */
Packit Service 20376f
	while (info->full.size > 0) {
Packit Service 20376f
		if (info->full.ptr[info->full.size - 1] != '/')
Packit Service 20376f
			break;
Packit Service 20376f
		info->full.size--;
Packit Service 20376f
	}
Packit Service 20376f
	info->full.ptr[info->full.size] = '\0';
Packit Service 20376f
Packit Service 20376f
	/* skip leading slashes in path */
Packit Service 20376f
	while (*info->path == '/')
Packit Service 20376f
		info->path++;
Packit Service 20376f
Packit Service 20376f
	/* find trailing basename component */
Packit Service 20376f
	info->basename = strrchr(info->path, '/');
Packit Service 20376f
	if (info->basename)
Packit Service 20376f
		info->basename++;
Packit Service 20376f
	if (!info->basename || !*info->basename)
Packit Service 20376f
		info->basename = info->path;
Packit Service 20376f
Packit Service 20376f
	switch (dir_flag)
Packit Service 20376f
	{
Packit Service 20376f
	case GIT_DIR_FLAG_FALSE:
Packit Service 20376f
		info->is_dir = 0;
Packit Service 20376f
		break;
Packit Service 20376f
Packit Service 20376f
	case GIT_DIR_FLAG_TRUE:
Packit Service 20376f
		info->is_dir = 1;
Packit Service 20376f
		break;
Packit Service 20376f
Packit Service 20376f
	case GIT_DIR_FLAG_UNKNOWN:
Packit Service 20376f
	default:
Packit Service 20376f
		info->is_dir = (int)git_path_isdir(info->full.ptr);
Packit Service 20376f
		break;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	return 0;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
void git_attr_path__free(git_attr_path *info)
Packit Service 20376f
{
Packit Service 20376f
	git_buf_free(&info->full);
Packit Service 20376f
	info->path = NULL;
Packit Service 20376f
	info->basename = NULL;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
/*
Packit Service 20376f
 * From gitattributes(5):
Packit Service 20376f
 *
Packit Service 20376f
 * Patterns have the following format:
Packit Service 20376f
 *
Packit Service 20376f
 * - A blank line matches no files, so it can serve as a separator for
Packit Service 20376f
 *   readability.
Packit Service 20376f
 *
Packit Service 20376f
 * - A line starting with # serves as a comment.
Packit Service 20376f
 *
Packit Service 20376f
 * - An optional prefix ! which negates the pattern; any matching file
Packit Service 20376f
 *   excluded by a previous pattern will become included again. If a negated
Packit Service 20376f
 *   pattern matches, this will override lower precedence patterns sources.
Packit Service 20376f
 *
Packit Service 20376f
 * - If the pattern ends with a slash, it is removed for the purpose of the
Packit Service 20376f
 *   following description, but it would only find a match with a directory. In
Packit Service 20376f
 *   other words, foo/ will match a directory foo and paths underneath it, but
Packit Service 20376f
 *   will not match a regular file or a symbolic link foo (this is consistent
Packit Service 20376f
 *   with the way how pathspec works in general in git).
Packit Service 20376f
 *
Packit Service 20376f
 * - If the pattern does not contain a slash /, git treats it as a shell glob
Packit Service 20376f
 *   pattern and checks for a match against the pathname without leading
Packit Service 20376f
 *   directories.
Packit Service 20376f
 *
Packit Service 20376f
 * - Otherwise, git treats the pattern as a shell glob suitable for consumption
Packit Service 20376f
 *   by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will
Packit Service 20376f
 *   not match a / in the pathname. For example, "Documentation/\*.html" matches
Packit Service 20376f
 *   "Documentation/git.html" but not "Documentation/ppc/ppc.html". A leading
Packit Service 20376f
 *   slash matches the beginning of the pathname; for example, "/\*.c" matches
Packit Service 20376f
 *   "cat-file.c" but not "mozilla-sha1/sha1.c".
Packit Service 20376f
 */
Packit Service 20376f
Packit Service 20376f
/*
Packit Service 20376f
 * This will return 0 if the spec was filled out,
Packit Service 20376f
 * GIT_ENOTFOUND if the fnmatch does not require matching, or
Packit Service 20376f
 * another error code there was an actual problem.
Packit Service 20376f
 */
Packit Service 20376f
int git_attr_fnmatch__parse(
Packit Service 20376f
	git_attr_fnmatch *spec,
Packit Service 20376f
	git_pool *pool,
Packit Service 20376f
	const char *context,
Packit Service 20376f
	const char **base)
Packit Service 20376f
{
Packit Service 20376f
	const char *pattern, *scan;
Packit Service 20376f
	int slash_count, allow_space;
Packit Service 20376f
Packit Service 20376f
	assert(spec && base && *base);
Packit Service 20376f
Packit Service 20376f
	if (parse_optimized_patterns(spec, pool, *base))
Packit Service 20376f
		return 0;
Packit Service 20376f
Packit Service 20376f
	spec->flags = (spec->flags & GIT_ATTR_FNMATCH__INCOMING);
Packit Service 20376f
	allow_space = ((spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE) != 0);
Packit Service 20376f
Packit Service 20376f
	pattern = *base;
Packit Service 20376f
Packit Service 20376f
	while (git__isspace(*pattern)) pattern++;
Packit Service 20376f
	if (!*pattern || *pattern == '#') {
Packit Service 20376f
		*base = git__next_line(pattern);
Packit Service 20376f
		return GIT_ENOTFOUND;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	if (*pattern == '[' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWMACRO) != 0) {
Packit Service 20376f
		if (strncmp(pattern, "[attr]", 6) == 0) {
Packit Service 20376f
			spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO;
Packit Service 20376f
			pattern += 6;
Packit Service 20376f
		}
Packit Service 20376f
		/* else a character range like [a-e]* which is accepted */
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	if (*pattern == '!' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWNEG) != 0) {
Packit Service 20376f
		spec->flags = spec->flags |
Packit Service 20376f
			GIT_ATTR_FNMATCH_NEGATIVE | GIT_ATTR_FNMATCH_LEADINGDIR;
Packit Service 20376f
		pattern++;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	slash_count = 0;
Packit Service 20376f
	for (scan = pattern; *scan != '\0'; ++scan) {
Packit Service 20376f
		/* scan until (non-escaped) white space */
Packit Service 20376f
		if (git__isspace(*scan) && *(scan - 1) != '\\') {
Packit Service 20376f
			if (!allow_space || (*scan != ' ' && *scan != '\t' && *scan != '\r'))
Packit Service 20376f
				break;
Packit Service 20376f
		}
Packit Service 20376f
Packit Service 20376f
		if (*scan == '/') {
Packit Service 20376f
			spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH;
Packit Service 20376f
			slash_count++;
Packit Service 20376f
			if (pattern == scan)
Packit Service 20376f
				pattern++;
Packit Service 20376f
		}
Packit Service 20376f
		/* remember if we see an unescaped wildcard in pattern */
Packit Service 20376f
		else if (git__iswildcard(*scan) &&
Packit Service 20376f
			(scan == pattern || (*(scan - 1) != '\\')))
Packit Service 20376f
			spec->flags = spec->flags | GIT_ATTR_FNMATCH_HASWILD;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	*base = scan;
Packit Service 20376f
Packit Service 20376f
	if ((spec->length = scan - pattern) == 0)
Packit Service 20376f
		return GIT_ENOTFOUND;
Packit Service 20376f
Packit Service 20376f
	/*
Packit Service 20376f
	 * Remove one trailing \r in case this is a CRLF delimited
Packit Service 20376f
	 * file, in the case of Icon\r\r\n, we still leave the first
Packit Service 20376f
	 * \r there to match against.
Packit Service 20376f
	 */
Packit Service 20376f
	if (pattern[spec->length - 1] == '\r')
Packit Service 20376f
		if (--spec->length == 0)
Packit Service 20376f
			return GIT_ENOTFOUND;
Packit Service 20376f
Packit Service 20376f
	if (pattern[spec->length - 1] == '/') {
Packit Service 20376f
		spec->length--;
Packit Service 20376f
		spec->flags = spec->flags | GIT_ATTR_FNMATCH_DIRECTORY;
Packit Service 20376f
		if (--slash_count <= 0)
Packit Service 20376f
			spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH;
Packit Service 20376f
	}
Packit Service 20376f
	if ((spec->flags & GIT_ATTR_FNMATCH_NOLEADINGDIR) == 0 &&
Packit Service 20376f
		spec->length >= 2 &&
Packit Service 20376f
		pattern[spec->length - 1] == '*' &&
Packit Service 20376f
		pattern[spec->length - 2] == '/') {
Packit Service 20376f
		spec->length -= 2;
Packit Service 20376f
		spec->flags = spec->flags | GIT_ATTR_FNMATCH_LEADINGDIR;
Packit Service 20376f
		/* leave FULLPATH match on, however */
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	if (context) {
Packit Service 20376f
		char *slash = strrchr(context, '/');
Packit Service 20376f
		size_t len;
Packit Service 20376f
		if (slash) {
Packit Service 20376f
			/* include the slash for easier matching */
Packit Service 20376f
			len = slash - context + 1;
Packit Service 20376f
			spec->containing_dir = git_pool_strndup(pool, context, len);
Packit Service 20376f
			spec->containing_dir_length = len;
Packit Service 20376f
		}
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	spec->pattern = git_pool_strndup(pool, pattern, spec->length);
Packit Service 20376f
Packit Service 20376f
	if (!spec->pattern) {
Packit Service 20376f
		*base = git__next_line(pattern);
Packit Service 20376f
		return -1;
Packit Service 20376f
	} else {
Packit Service 20376f
		/* strip '\' that might have be used for internal whitespace */
Packit Service 20376f
		spec->length = git__unescape(spec->pattern);
Packit Service 20376f
		/* TODO: convert remaining '\' into '/' for POSIX ??? */
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	return 0;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
static bool parse_optimized_patterns(
Packit Service 20376f
	git_attr_fnmatch *spec,
Packit Service 20376f
	git_pool *pool,
Packit Service 20376f
	const char *pattern)
Packit Service 20376f
{
Packit Service 20376f
	if (!pattern[1] && (pattern[0] == '*' || pattern[0] == '.')) {
Packit Service 20376f
		spec->flags = GIT_ATTR_FNMATCH_MATCH_ALL;
Packit Service 20376f
		spec->pattern = git_pool_strndup(pool, pattern, 1);
Packit Service 20376f
		spec->length = 1;
Packit Service 20376f
Packit Service 20376f
		return true;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	return false;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
static int sort_by_hash_and_name(const void *a_raw, const void *b_raw)
Packit Service 20376f
{
Packit Service 20376f
	const git_attr_name *a = a_raw;
Packit Service 20376f
	const git_attr_name *b = b_raw;
Packit Service 20376f
Packit Service 20376f
	if (b->name_hash < a->name_hash)
Packit Service 20376f
		return 1;
Packit Service 20376f
	else if (b->name_hash > a->name_hash)
Packit Service 20376f
		return -1;
Packit Service 20376f
	else
Packit Service 20376f
		return strcmp(b->name, a->name);
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
static void git_attr_assignment__free(git_attr_assignment *assign)
Packit Service 20376f
{
Packit Service 20376f
	/* name and value are stored in a git_pool associated with the
Packit Service 20376f
	 * git_attr_file, so they do not need to be freed here
Packit Service 20376f
	 */
Packit Service 20376f
	assign->name = NULL;
Packit Service 20376f
	assign->value = NULL;
Packit Service 20376f
	git__free(assign);
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
static int merge_assignments(void **old_raw, void *new_raw)
Packit Service 20376f
{
Packit Service 20376f
	git_attr_assignment **old = (git_attr_assignment **)old_raw;
Packit Service 20376f
	git_attr_assignment *new = (git_attr_assignment *)new_raw;
Packit Service 20376f
Packit Service 20376f
	GIT_REFCOUNT_DEC(*old, git_attr_assignment__free);
Packit Service 20376f
	*old = new;
Packit Service 20376f
	return GIT_EEXISTS;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
int git_attr_assignment__parse(
Packit Service 20376f
	git_repository *repo,
Packit Service 20376f
	git_pool *pool,
Packit Service 20376f
	git_vector *assigns,
Packit Service 20376f
	const char **base)
Packit Service 20376f
{
Packit Service 20376f
	int error;
Packit Service 20376f
	const char *scan = *base;
Packit Service 20376f
	git_attr_assignment *assign = NULL;
Packit Service 20376f
Packit Service 20376f
	assert(assigns && !assigns->length);
Packit Service 20376f
Packit Service 20376f
	git_vector_set_cmp(assigns, sort_by_hash_and_name);
Packit Service 20376f
Packit Service 20376f
	while (*scan && *scan != '\n') {
Packit Service 20376f
		const char *name_start, *value_start;
Packit Service 20376f
Packit Service 20376f
		/* skip leading blanks */
Packit Service 20376f
		while (git__isspace(*scan) && *scan != '\n') scan++;
Packit Service 20376f
Packit Service 20376f
		/* allocate assign if needed */
Packit Service 20376f
		if (!assign) {
Packit Service 20376f
			assign = git__calloc(1, sizeof(git_attr_assignment));
Packit Service 20376f
			GITERR_CHECK_ALLOC(assign);
Packit Service 20376f
			GIT_REFCOUNT_INC(assign);
Packit Service 20376f
		}
Packit Service 20376f
Packit Service 20376f
		assign->name_hash = 5381;
Packit Service 20376f
		assign->value = git_attr__true;
Packit Service 20376f
Packit Service 20376f
		/* look for magic name prefixes */
Packit Service 20376f
		if (*scan == '-') {
Packit Service 20376f
			assign->value = git_attr__false;
Packit Service 20376f
			scan++;
Packit Service 20376f
		} else if (*scan == '!') {
Packit Service 20376f
			assign->value = git_attr__unset; /* explicit unspecified state */
Packit Service 20376f
			scan++;
Packit Service 20376f
		} else if (*scan == '#') /* comment rest of line */
Packit Service 20376f
			break;
Packit Service 20376f
Packit Service 20376f
		/* find the name */
Packit Service 20376f
		name_start = scan;
Packit Service 20376f
		while (*scan && !git__isspace(*scan) && *scan != '=') {
Packit Service 20376f
			assign->name_hash =
Packit Service 20376f
				((assign->name_hash << 5) + assign->name_hash) + *scan;
Packit Service 20376f
			scan++;
Packit Service 20376f
		}
Packit Service 20376f
		if (scan == name_start) {
Packit Service 20376f
			/* must have found lone prefix (" - ") or leading = ("=foo")
Packit Service 20376f
			 * or end of buffer -- advance until whitespace and continue
Packit Service 20376f
			 */
Packit Service 20376f
			while (*scan && !git__isspace(*scan)) scan++;
Packit Service 20376f
			continue;
Packit Service 20376f
		}
Packit Service 20376f
Packit Service 20376f
		/* allocate permanent storage for name */
Packit Service 20376f
		assign->name = git_pool_strndup(pool, name_start, scan - name_start);
Packit Service 20376f
		GITERR_CHECK_ALLOC(assign->name);
Packit Service 20376f
Packit Service 20376f
		/* if there is an equals sign, find the value */
Packit Service 20376f
		if (*scan == '=') {
Packit Service 20376f
			for (value_start = ++scan; *scan && !git__isspace(*scan); ++scan);
Packit Service 20376f
Packit Service 20376f
			/* if we found a value, allocate permanent storage for it */
Packit Service 20376f
			if (scan > value_start) {
Packit Service 20376f
				assign->value = git_pool_strndup(pool, value_start, scan - value_start);
Packit Service 20376f
				GITERR_CHECK_ALLOC(assign->value);
Packit Service 20376f
			}
Packit Service 20376f
		}
Packit Service 20376f
Packit Service 20376f
		/* expand macros (if given a repo with a macro cache) */
Packit Service 20376f
		if (repo != NULL && assign->value == git_attr__true) {
Packit Service 20376f
			git_attr_rule *macro =
Packit Service 20376f
				git_attr_cache__lookup_macro(repo, assign->name);
Packit Service 20376f
Packit Service 20376f
			if (macro != NULL) {
Packit Service 20376f
				unsigned int i;
Packit Service 20376f
				git_attr_assignment *massign;
Packit Service 20376f
Packit Service 20376f
				git_vector_foreach(&macro->assigns, i, massign) {
Packit Service 20376f
					GIT_REFCOUNT_INC(massign);
Packit Service 20376f
Packit Service 20376f
					error = git_vector_insert_sorted(
Packit Service 20376f
						assigns, massign, &merge_assignments);
Packit Service 20376f
					if (error < 0 && error != GIT_EEXISTS) {
Packit Service 20376f
						git_attr_assignment__free(assign);
Packit Service 20376f
						return error;
Packit Service 20376f
					}
Packit Service 20376f
				}
Packit Service 20376f
			}
Packit Service 20376f
		}
Packit Service 20376f
Packit Service 20376f
		/* insert allocated assign into vector */
Packit Service 20376f
		error = git_vector_insert_sorted(assigns, assign, &merge_assignments);
Packit Service 20376f
		if (error < 0 && error != GIT_EEXISTS)
Packit Service 20376f
			return error;
Packit Service 20376f
Packit Service 20376f
		/* clear assign since it is now "owned" by the vector */
Packit Service 20376f
		assign = NULL;
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	if (assign != NULL)
Packit Service 20376f
		git_attr_assignment__free(assign);
Packit Service 20376f
Packit Service 20376f
	*base = git__next_line(scan);
Packit Service 20376f
Packit Service 20376f
	return (assigns->length == 0) ? GIT_ENOTFOUND : 0;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
static void git_attr_rule__clear(git_attr_rule *rule)
Packit Service 20376f
{
Packit Service 20376f
	unsigned int i;
Packit Service 20376f
	git_attr_assignment *assign;
Packit Service 20376f
Packit Service 20376f
	if (!rule)
Packit Service 20376f
		return;
Packit Service 20376f
Packit Service 20376f
	if (!(rule->match.flags & GIT_ATTR_FNMATCH_IGNORE)) {
Packit Service 20376f
		git_vector_foreach(&rule->assigns, i, assign)
Packit Service 20376f
			GIT_REFCOUNT_DEC(assign, git_attr_assignment__free);
Packit Service 20376f
		git_vector_free(&rule->assigns);
Packit Service 20376f
	}
Packit Service 20376f
Packit Service 20376f
	/* match.pattern is stored in a git_pool, so no need to free */
Packit Service 20376f
	rule->match.pattern = NULL;
Packit Service 20376f
	rule->match.length = 0;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
void git_attr_rule__free(git_attr_rule *rule)
Packit Service 20376f
{
Packit Service 20376f
	git_attr_rule__clear(rule);
Packit Service 20376f
	git__free(rule);
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
int git_attr_session__init(git_attr_session *session, git_repository *repo)
Packit Service 20376f
{
Packit Service 20376f
	assert(repo);
Packit Service 20376f
Packit Service 20376f
	session->key = git_atomic_inc(&repo->attr_session_key);
Packit Service 20376f
Packit Service 20376f
	return 0;
Packit Service 20376f
}
Packit Service 20376f
Packit Service 20376f
void git_attr_session__free(git_attr_session *session)
Packit Service 20376f
{
Packit Service 20376f
	if (!session)
Packit Service 20376f
		return;
Packit Service 20376f
Packit Service 20376f
	git_buf_free(&session->sysdir);
Packit Service 20376f
	git_buf_free(&session->tmp);
Packit Service 20376f
Packit Service 20376f
	memset(session, 0, sizeof(git_attr_session));
Packit Service 20376f
}