mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-06 23:44:06 +00:00

Ended up migrating a bunch of upstream functions as well including vector, attr_file, and odb in order to get this to work right.
558 lines
13 KiB
C
558 lines
13 KiB
C
#include "common.h"
|
|
#include "repository.h"
|
|
#include "filebuf.h"
|
|
#include <ctype.h>
|
|
|
|
const char *git_attr__true = "[internal]__TRUE__";
|
|
const char *git_attr__false = "[internal]__FALSE__";
|
|
|
|
static int sort_by_hash_and_name(const void *a_raw, const void *b_raw);
|
|
static void git_attr_rule__clear(git_attr_rule *rule);
|
|
|
|
int git_attr_file__new(git_attr_file **attrs_ptr)
|
|
{
|
|
git_attr_file *attrs = NULL;
|
|
|
|
attrs = git__calloc(1, sizeof(git_attr_file));
|
|
GITERR_CHECK_ALLOC(attrs);
|
|
|
|
if (git_vector_init(&attrs->rules, 4, NULL) < 0) {
|
|
git__free(attrs);
|
|
attrs = NULL;
|
|
}
|
|
|
|
*attrs_ptr = attrs;
|
|
|
|
return attrs ? 0 : -1;
|
|
}
|
|
|
|
int git_attr_file__set_path(
|
|
git_repository *repo, const char *path, git_attr_file *file)
|
|
{
|
|
if (file->path != NULL) {
|
|
git__free(file->path);
|
|
file->path = NULL;
|
|
}
|
|
|
|
if (repo == NULL)
|
|
file->path = git__strdup(path);
|
|
else {
|
|
const char *workdir = git_repository_workdir(repo);
|
|
|
|
if (workdir && git__prefixcmp(path, workdir) == 0)
|
|
file->path = git__strdup(path + strlen(workdir));
|
|
else
|
|
file->path = git__strdup(path);
|
|
}
|
|
|
|
GITERR_CHECK_ALLOC(file->path);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int git_attr_file__from_buffer(
|
|
git_repository *repo, const char *buffer, git_attr_file *attrs)
|
|
{
|
|
int error = 0;
|
|
const char *scan = NULL;
|
|
char *context = NULL;
|
|
git_attr_rule *rule = NULL;
|
|
|
|
assert(buffer && attrs);
|
|
|
|
scan = buffer;
|
|
|
|
if (attrs->path && git__suffixcmp(attrs->path, GIT_ATTR_FILE) == 0) {
|
|
context = git__strndup(attrs->path,
|
|
strlen(attrs->path) - strlen(GIT_ATTR_FILE));
|
|
GITERR_CHECK_ALLOC(context);
|
|
}
|
|
|
|
while (!error && *scan) {
|
|
/* allocate rule if needed */
|
|
if (!rule && !(rule = git__calloc(1, sizeof(git_attr_rule)))) {
|
|
error = -1;
|
|
break;
|
|
}
|
|
|
|
/* parse the next "pattern attr attr attr" line */
|
|
if (!(error = git_attr_fnmatch__parse(&rule->match, context, &scan)) &&
|
|
!(error = git_attr_assignment__parse(repo, &rule->assigns, &scan)))
|
|
{
|
|
if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO)
|
|
/* should generate error/warning if this is coming from any
|
|
* file other than .gitattributes at repo root.
|
|
*/
|
|
error = git_attr_cache__insert_macro(repo, rule);
|
|
else
|
|
error = git_vector_insert(&attrs->rules, rule);
|
|
}
|
|
|
|
/* if the rule wasn't a pattern, on to the next */
|
|
if (error < 0) {
|
|
git_attr_rule__clear(rule); /* reset rule contents */
|
|
if (error == GIT_ENOTFOUND)
|
|
error = 0;
|
|
} else {
|
|
rule = NULL; /* vector now "owns" the rule */
|
|
}
|
|
}
|
|
|
|
git_attr_rule__free(rule);
|
|
git__free(context);
|
|
|
|
return error;
|
|
}
|
|
|
|
int git_attr_file__from_file(
|
|
git_repository *repo, const char *path, git_attr_file *file)
|
|
{
|
|
int error;
|
|
git_buf fbuf = GIT_BUF_INIT;
|
|
|
|
assert(path && file);
|
|
|
|
if (file->path == NULL && git_attr_file__set_path(repo, path, file) < 0)
|
|
return -1;
|
|
|
|
if (git_futils_readbuffer(&fbuf, path) < 0)
|
|
return -1;
|
|
|
|
error = git_attr_file__from_buffer(repo, fbuf.ptr, file);
|
|
|
|
git_buf_free(&fbuf);
|
|
|
|
return error;
|
|
}
|
|
|
|
void git_attr_file__free(git_attr_file *file)
|
|
{
|
|
unsigned int i;
|
|
git_attr_rule *rule;
|
|
|
|
if (!file)
|
|
return;
|
|
|
|
git_vector_foreach(&file->rules, i, rule)
|
|
git_attr_rule__free(rule);
|
|
|
|
git_vector_free(&file->rules);
|
|
|
|
git__free(file->path);
|
|
file->path = NULL;
|
|
|
|
git__free(file);
|
|
}
|
|
|
|
unsigned long git_attr_file__name_hash(const char *name)
|
|
{
|
|
unsigned long h = 5381;
|
|
int c;
|
|
assert(name);
|
|
while ((c = (int)*name++) != 0)
|
|
h = ((h << 5) + h) + c;
|
|
return h;
|
|
}
|
|
|
|
|
|
int git_attr_file__lookup_one(
|
|
git_attr_file *file,
|
|
const git_attr_path *path,
|
|
const char *attr,
|
|
const char **value)
|
|
{
|
|
unsigned int i;
|
|
git_attr_name name;
|
|
git_attr_rule *rule;
|
|
|
|
*value = NULL;
|
|
|
|
name.name = attr;
|
|
name.name_hash = git_attr_file__name_hash(attr);
|
|
|
|
git_attr_file__foreach_matching_rule(file, path, i, rule) {
|
|
int pos = git_vector_bsearch(&rule->assigns, &name);
|
|
|
|
if (pos >= 0) {
|
|
*value = ((git_attr_assignment *)
|
|
git_vector_get(&rule->assigns, pos))->value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return GIT_SUCCESS;
|
|
}
|
|
|
|
|
|
int git_attr_fnmatch__match(
|
|
git_attr_fnmatch *match,
|
|
const git_attr_path *path)
|
|
{
|
|
int matched = FNM_NOMATCH;
|
|
|
|
if (match->flags & GIT_ATTR_FNMATCH_DIRECTORY && !path->is_dir)
|
|
return matched;
|
|
|
|
if (match->flags & GIT_ATTR_FNMATCH_FULLPATH)
|
|
matched = p_fnmatch(match->pattern, path->path, FNM_PATHNAME);
|
|
else if (path->is_dir)
|
|
matched = p_fnmatch(match->pattern, path->basename, FNM_LEADING_DIR);
|
|
else
|
|
matched = p_fnmatch(match->pattern, path->basename, 0);
|
|
|
|
return matched;
|
|
}
|
|
|
|
int git_attr_rule__match(
|
|
git_attr_rule *rule,
|
|
const git_attr_path *path)
|
|
{
|
|
int matched = git_attr_fnmatch__match(&rule->match, path);
|
|
|
|
if (rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE)
|
|
matched = (matched == GIT_SUCCESS) ? FNM_NOMATCH : GIT_SUCCESS;
|
|
|
|
return matched;
|
|
}
|
|
|
|
|
|
git_attr_assignment *git_attr_rule__lookup_assignment(
|
|
git_attr_rule *rule, const char *name)
|
|
{
|
|
int pos;
|
|
git_attr_name key;
|
|
key.name = name;
|
|
key.name_hash = git_attr_file__name_hash(name);
|
|
|
|
pos = git_vector_bsearch(&rule->assigns, &key);
|
|
|
|
return (pos >= 0) ? git_vector_get(&rule->assigns, pos) : NULL;
|
|
}
|
|
|
|
int git_attr_path__init(
|
|
git_attr_path *info, const char *path, const char *base)
|
|
{
|
|
assert(info && path);
|
|
info->path = path;
|
|
info->basename = strrchr(path, '/');
|
|
if (info->basename)
|
|
info->basename++;
|
|
if (!info->basename || !*info->basename)
|
|
info->basename = path;
|
|
|
|
if (base != NULL && git_path_root(path) < 0) {
|
|
git_buf full_path = GIT_BUF_INIT;
|
|
if (git_buf_joinpath(&full_path, base, path) < 0)
|
|
return -1;
|
|
info->is_dir = (int)git_path_isdir(full_path.ptr);
|
|
git_buf_free(&full_path);
|
|
return 0;
|
|
}
|
|
info->is_dir = (int)git_path_isdir(path);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* From gitattributes(5):
|
|
*
|
|
* Patterns have the following format:
|
|
*
|
|
* - A blank line matches no files, so it can serve as a separator for
|
|
* readability.
|
|
*
|
|
* - A line starting with # serves as a comment.
|
|
*
|
|
* - An optional prefix ! which negates the pattern; any matching file
|
|
* excluded by a previous pattern will become included again. If a negated
|
|
* pattern matches, this will override lower precedence patterns sources.
|
|
*
|
|
* - If the pattern ends with a slash, it is removed for the purpose of the
|
|
* following description, but it would only find a match with a directory. In
|
|
* other words, foo/ will match a directory foo and paths underneath it, but
|
|
* will not match a regular file or a symbolic link foo (this is consistent
|
|
* with the way how pathspec works in general in git).
|
|
*
|
|
* - If the pattern does not contain a slash /, git treats it as a shell glob
|
|
* pattern and checks for a match against the pathname without leading
|
|
* directories.
|
|
*
|
|
* - Otherwise, git treats the pattern as a shell glob suitable for consumption
|
|
* by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will
|
|
* not match a / in the pathname. For example, "Documentation/\*.html" matches
|
|
* "Documentation/git.html" but not "Documentation/ppc/ppc.html". A leading
|
|
* slash matches the beginning of the pathname; for example, "/\*.c" matches
|
|
* "cat-file.c" but not "mozilla-sha1/sha1.c".
|
|
*/
|
|
|
|
/*
|
|
* This will return GIT_SUCCESS if the spec was filled out,
|
|
* GIT_ENOTFOUND if the fnmatch does not require matching, or
|
|
* another error code there was an actual problem.
|
|
*/
|
|
int git_attr_fnmatch__parse(
|
|
git_attr_fnmatch *spec,
|
|
const char *source,
|
|
const char **base)
|
|
{
|
|
const char *pattern, *scan;
|
|
int slash_count;
|
|
|
|
assert(spec && base && *base);
|
|
|
|
pattern = *base;
|
|
|
|
while (isspace(*pattern)) pattern++;
|
|
if (!*pattern || *pattern == '#') {
|
|
*base = git__next_line(pattern);
|
|
return GIT_ENOTFOUND;
|
|
}
|
|
|
|
spec->flags = 0;
|
|
|
|
if (*pattern == '[') {
|
|
if (strncmp(pattern, "[attr]", 6) == 0) {
|
|
spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO;
|
|
pattern += 6;
|
|
}
|
|
/* else a character range like [a-e]* which is accepted */
|
|
}
|
|
|
|
if (*pattern == '!') {
|
|
spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE;
|
|
pattern++;
|
|
}
|
|
|
|
slash_count = 0;
|
|
for (scan = pattern; *scan != '\0'; ++scan) {
|
|
/* scan until (non-escaped) white space */
|
|
if (isspace(*scan) && *(scan - 1) != '\\')
|
|
break;
|
|
|
|
if (*scan == '/') {
|
|
spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH;
|
|
slash_count++;
|
|
}
|
|
}
|
|
|
|
*base = scan;
|
|
|
|
spec->length = scan - pattern;
|
|
|
|
if (pattern[spec->length - 1] == '/') {
|
|
spec->length--;
|
|
spec->flags = spec->flags | GIT_ATTR_FNMATCH_DIRECTORY;
|
|
if (--slash_count <= 0)
|
|
spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH;
|
|
}
|
|
|
|
if ((spec->flags & GIT_ATTR_FNMATCH_FULLPATH) != 0 &&
|
|
source != NULL && git_path_root(pattern) < 0)
|
|
{
|
|
size_t sourcelen = strlen(source);
|
|
/* given an unrooted fullpath match from a file inside a repo,
|
|
* prefix the pattern with the relative directory of the source file
|
|
*/
|
|
spec->pattern = git__malloc(sourcelen + spec->length + 1);
|
|
if (spec->pattern) {
|
|
memcpy(spec->pattern, source, sourcelen);
|
|
memcpy(spec->pattern + sourcelen, pattern, spec->length);
|
|
spec->length += sourcelen;
|
|
spec->pattern[spec->length] = '\0';
|
|
}
|
|
} else {
|
|
spec->pattern = git__strndup(pattern, spec->length);
|
|
}
|
|
|
|
if (!spec->pattern) {
|
|
*base = git__next_line(pattern);
|
|
return -1;
|
|
} else {
|
|
/* strip '\' that might have be used for internal whitespace */
|
|
char *to = spec->pattern;
|
|
for (scan = spec->pattern; *scan; to++, scan++) {
|
|
if (*scan == '\\')
|
|
scan++; /* skip '\' but include next char */
|
|
if (to != scan)
|
|
*to = *scan;
|
|
}
|
|
if (to != scan) {
|
|
*to = '\0';
|
|
spec->length = (to - spec->pattern);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sort_by_hash_and_name(const void *a_raw, const void *b_raw)
|
|
{
|
|
const git_attr_name *a = a_raw;
|
|
const git_attr_name *b = b_raw;
|
|
|
|
if (b->name_hash < a->name_hash)
|
|
return 1;
|
|
else if (b->name_hash > a->name_hash)
|
|
return -1;
|
|
else
|
|
return strcmp(b->name, a->name);
|
|
}
|
|
|
|
static void git_attr_assignment__free(git_attr_assignment *assign)
|
|
{
|
|
git__free(assign->name);
|
|
assign->name = NULL;
|
|
|
|
if (assign->is_allocated) {
|
|
git__free((void *)assign->value);
|
|
assign->value = NULL;
|
|
}
|
|
|
|
git__free(assign);
|
|
}
|
|
|
|
static int merge_assignments(void **old_raw, void *new_raw)
|
|
{
|
|
git_attr_assignment **old = (git_attr_assignment **)old_raw;
|
|
git_attr_assignment *new = (git_attr_assignment *)new_raw;
|
|
|
|
GIT_REFCOUNT_DEC(*old, git_attr_assignment__free);
|
|
*old = new;
|
|
return GIT_EEXISTS;
|
|
}
|
|
|
|
int git_attr_assignment__parse(
|
|
git_repository *repo,
|
|
git_vector *assigns,
|
|
const char **base)
|
|
{
|
|
int error;
|
|
const char *scan = *base;
|
|
git_attr_assignment *assign = NULL;
|
|
|
|
assert(assigns && !assigns->length);
|
|
|
|
assigns->_cmp = sort_by_hash_and_name;
|
|
|
|
while (*scan && *scan != '\n') {
|
|
const char *name_start, *value_start;
|
|
|
|
/* skip leading blanks */
|
|
while (isspace(*scan) && *scan != '\n') scan++;
|
|
|
|
/* allocate assign if needed */
|
|
if (!assign) {
|
|
assign = git__calloc(1, sizeof(git_attr_assignment));
|
|
GITERR_CHECK_ALLOC(assign);
|
|
GIT_REFCOUNT_INC(assign);
|
|
}
|
|
|
|
assign->name_hash = 5381;
|
|
assign->value = git_attr__true;
|
|
assign->is_allocated = 0;
|
|
|
|
/* look for magic name prefixes */
|
|
if (*scan == '-') {
|
|
assign->value = git_attr__false;
|
|
scan++;
|
|
} else if (*scan == '!') {
|
|
assign->value = NULL; /* explicit unspecified state */
|
|
scan++;
|
|
} else if (*scan == '#') /* comment rest of line */
|
|
break;
|
|
|
|
/* find the name */
|
|
name_start = scan;
|
|
while (*scan && !isspace(*scan) && *scan != '=') {
|
|
assign->name_hash =
|
|
((assign->name_hash << 5) + assign->name_hash) + *scan;
|
|
scan++;
|
|
}
|
|
if (scan == name_start) {
|
|
/* must have found lone prefix (" - ") or leading = ("=foo")
|
|
* or end of buffer -- advance until whitespace and continue
|
|
*/
|
|
while (*scan && !isspace(*scan)) scan++;
|
|
continue;
|
|
}
|
|
|
|
/* allocate permanent storage for name */
|
|
assign->name = git__strndup(name_start, scan - name_start);
|
|
GITERR_CHECK_ALLOC(assign->name);
|
|
|
|
/* if there is an equals sign, find the value */
|
|
if (*scan == '=') {
|
|
for (value_start = ++scan; *scan && !isspace(*scan); ++scan);
|
|
|
|
/* if we found a value, allocate permanent storage for it */
|
|
if (scan > value_start) {
|
|
assign->value = git__strndup(value_start, scan - value_start);
|
|
GITERR_CHECK_ALLOC(assign->value);
|
|
assign->is_allocated = 1;
|
|
}
|
|
}
|
|
|
|
/* expand macros (if given a repo with a macro cache) */
|
|
if (repo != NULL && assign->value == git_attr__true) {
|
|
git_attr_rule *macro =
|
|
git_hashtable_lookup(repo->attrcache.macros, assign->name);
|
|
|
|
if (macro != NULL) {
|
|
unsigned int i;
|
|
git_attr_assignment *massign;
|
|
|
|
git_vector_foreach(¯o->assigns, i, massign) {
|
|
GIT_REFCOUNT_INC(massign);
|
|
|
|
error = git_vector_insert_sorted(
|
|
assigns, massign, &merge_assignments);
|
|
if (error < 0 && error != GIT_EEXISTS)
|
|
return error;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* insert allocated assign into vector */
|
|
error = git_vector_insert_sorted(assigns, assign, &merge_assignments);
|
|
if (error < 0 && error != GIT_EEXISTS)
|
|
return error;
|
|
|
|
/* clear assign since it is now "owned" by the vector */
|
|
assign = NULL;
|
|
}
|
|
|
|
if (assign != NULL)
|
|
git_attr_assignment__free(assign);
|
|
|
|
*base = git__next_line(scan);
|
|
|
|
return (assigns->length == 0) ? GIT_ENOTFOUND : 0;
|
|
}
|
|
|
|
static void git_attr_rule__clear(git_attr_rule *rule)
|
|
{
|
|
unsigned int i;
|
|
git_attr_assignment *assign;
|
|
|
|
if (!rule)
|
|
return;
|
|
|
|
if (!(rule->match.flags & GIT_ATTR_FNMATCH_IGNORE)) {
|
|
git_vector_foreach(&rule->assigns, i, assign)
|
|
GIT_REFCOUNT_DEC(assign, git_attr_assignment__free);
|
|
git_vector_free(&rule->assigns);
|
|
}
|
|
|
|
git__free(rule->match.pattern);
|
|
rule->match.pattern = NULL;
|
|
rule->match.length = 0;
|
|
}
|
|
|
|
void git_attr_rule__free(git_attr_rule *rule)
|
|
{
|
|
git_attr_rule__clear(rule);
|
|
git__free(rule);
|
|
}
|
|
|