diff options
| author | Russell Belfer <arrbee@arrbee.com> | 2011-12-16 10:56:43 -0800 |
|---|---|---|
| committer | Russell Belfer <arrbee@arrbee.com> | 2011-12-20 16:32:58 -0800 |
| commit | ee1f0b1aed7798908d9e038b006b66f868613fc3 (patch) | |
| tree | c60350029b9e4bb14811ac13caf59ad86424f33e /src | |
| parent | be00b00dd1468f1c625ca3fadc61f2a16edfb8d5 (diff) | |
| download | libgit2-ee1f0b1aed7798908d9e038b006b66f868613fc3.tar.gz | |
Add APIs for git attributes
This adds APIs for querying git attributes. In addition to
the new API in include/git2/attr.h, most of the action is in
src/attr_file.[hc] which contains utilities for dealing with
a single attributes file, and src/attr.[hc] which contains
the implementation of the APIs that merge all applicable
attributes files.
Diffstat (limited to 'src')
| -rw-r--r-- | src/attr.c | 311 | ||||
| -rw-r--r-- | src/attr.h | 21 | ||||
| -rw-r--r-- | src/attr_file.c | 456 | ||||
| -rw-r--r-- | src/attr_file.h | 87 | ||||
| -rw-r--r-- | src/hashtable.c | 14 | ||||
| -rw-r--r-- | src/hashtable.h | 7 | ||||
| -rw-r--r-- | src/refs.c | 15 | ||||
| -rw-r--r-- | src/repository.c | 1 | ||||
| -rw-r--r-- | src/repository.h | 2 | ||||
| -rw-r--r-- | src/util.h | 1 | ||||
| -rw-r--r-- | src/vector.c | 5 | ||||
| -rw-r--r-- | src/vector.h | 5 |
12 files changed, 911 insertions, 14 deletions
diff --git a/src/attr.c b/src/attr.c new file mode 100644 index 000000000..d8e7095b1 --- /dev/null +++ b/src/attr.c @@ -0,0 +1,311 @@ +#include "attr.h" +#include "buffer.h" +#include "fileops.h" +#include "config.h" +#include <ctype.h> + +#define GIT_ATTR_FILE_INREPO "info/attributes" +#define GIT_ATTR_FILE ".gitattributes" +#define GIT_ATTR_FILE_SYSTEM "/etc/gitattributes" +#if GIT_WIN32 +#define GIT_ATTR_FILE_WIN32 L"%PROGRAMFILES%\\Git\\etc\\gitattributes" +#endif + +static int collect_attr_files( + git_repository *repo, const char *path, git_vector *files); + + +int git_attr_get( + git_repository *repo, const char *pathname, + const char *name, const char **value) +{ + int error; + git_attr_path path; + git_vector files = GIT_VECTOR_INIT; + unsigned int i, j; + git_attr_file *file; + git_attr_name attr; + git_attr_rule *rule; + + *value = NULL; + + if ((error = git_attr_path__init(&path, pathname)) < GIT_SUCCESS || + (error = collect_attr_files(repo, pathname, &files)) < GIT_SUCCESS) + return git__rethrow(error, "Could not get attribute for %s", pathname); + + attr.name = name; + attr.name_hash = git_attr_file__name_hash(name); + + git_vector_foreach(&files, i, file) { + + git_attr_file__foreach_matching_rule(file, &path, j, rule) { + int pos = git_vector_bsearch(&rule->assigns, &attr); + git_clearerror(); /* okay if search failed */ + + if (pos >= 0) { + *value = ((git_attr_assignment *)git_vector_get( + &rule->assigns, pos))->value; + goto found; + } + } + } + +found: + git_vector_free(&files); + + return error; +} + + +typedef struct { + git_attr_name name; + git_attr_assignment *found; +} attr_get_many_info; + +int git_attr_get_many( + git_repository *repo, const char *pathname, + size_t num_attr, const char **names, const char **values) +{ + int error; + git_attr_path path; + git_vector files = GIT_VECTOR_INIT; + unsigned int i, j, k; + git_attr_file *file; + git_attr_rule *rule; + attr_get_many_info *info = NULL; + size_t num_found = 0; + + memset(values, 0, sizeof(const char *) * num_attr); + + if ((error = git_attr_path__init(&path, pathname)) < GIT_SUCCESS || + (error = collect_attr_files(repo, pathname, &files)) < GIT_SUCCESS) + return git__rethrow(error, "Could not get attributes for %s", pathname); + + if ((info = git__calloc(num_attr, sizeof(attr_get_many_info))) == NULL) { + git__rethrow(GIT_ENOMEM, "Could not get attributes for %s", pathname); + goto cleanup; + } + + git_vector_foreach(&files, i, file) { + + git_attr_file__foreach_matching_rule(file, &path, j, rule) { + + for (k = 0; k < num_attr; k++) { + int pos; + + if (info[k].found != NULL) /* already found assignment */ + continue; + + if (!info[k].name.name) { + info[k].name.name = names[k]; + info[k].name.name_hash = git_attr_file__name_hash(names[k]); + } + + pos = git_vector_bsearch(&rule->assigns, &info[k].name); + git_clearerror(); /* okay if search failed */ + + if (pos >= 0) { + info[k].found = (git_attr_assignment *) + git_vector_get(&rule->assigns, pos); + values[k] = info[k].found->value; + + if (++num_found == num_attr) + goto cleanup; + } + } + } + } + +cleanup: + git_vector_free(&files); + git__free(info); + + return error; +} + + +int git_attr_foreach( + git_repository *repo, const char *pathname, + int (*callback)(const char *name, const char *value, void *payload), + void *payload) +{ + int error; + git_attr_path path; + git_vector files = GIT_VECTOR_INIT; + unsigned int i, j, k; + git_attr_file *file; + git_attr_rule *rule; + git_attr_assignment *assign; + git_hashtable *seen = NULL; + + if ((error = git_attr_path__init(&path, pathname)) < GIT_SUCCESS || + (error = collect_attr_files(repo, pathname, &files)) < GIT_SUCCESS) + return git__rethrow(error, "Could not get attributes for %s", pathname); + + seen = git_hashtable_alloc(8, git_hash__strhash_cb, git_hash__strcmp_cb); + if (!seen) { + error = GIT_ENOMEM; + goto cleanup; + } + + git_vector_foreach(&files, i, file) { + + git_attr_file__foreach_matching_rule(file, &path, j, rule) { + + git_vector_foreach(&rule->assigns, k, assign) { + /* skip if higher priority assignment was already seen */ + if (git_hashtable_lookup(seen, assign->name)) + continue; + + error = git_hashtable_insert(seen, assign->name, assign); + if (error != GIT_SUCCESS) + goto cleanup; + + error = callback(assign->name, assign->value, payload); + if (error != GIT_SUCCESS) + goto cleanup; + } + } + } + +cleanup: + if (seen) + git_hashtable_free(seen); + git_vector_free(&files); + + if (error != GIT_SUCCESS) + (void)git__rethrow(error, "Could not get attributes for %s", pathname); + + return error; +} + + +/* add git_attr_file to vector of files, loading if needed */ +static int push_attrs( + git_repository *repo, + git_vector *files, + const char *base, + const char *filename) +{ + int error = GIT_SUCCESS; + git_attr_cache *cache = &repo->attrcache; + git_buf path = GIT_BUF_INIT; + git_attr_file *file; + int add_to_cache = 0; + + if (cache->files == NULL) { + cache->files = git_hashtable_alloc( + 8, git_hash__strhash_cb, git_hash__strcmp_cb); + if (!cache->files) + return git__throw(GIT_ENOMEM, "Could not create attribute cache"); + } + + if ((error = git_path_prettify(&path, filename, base)) < GIT_SUCCESS) { + if (error == GIT_EOSERR) + /* file was not found -- ignore error */ + error = GIT_SUCCESS; + goto cleanup; + } + + /* either get attr_file from cache or read from disk */ + file = git_hashtable_lookup(cache->files, path.ptr); + if (file == NULL) { + error = git_attr_file__from_file(&file, path.ptr); + add_to_cache = (error == GIT_SUCCESS); + } + + if (file != NULL) { + /* add file to vector, if we found it */ + error = git_vector_insert(files, file); + + /* add file to cache, if it is new */ + /* do this after above step b/c it is not critical */ + if (error == GIT_SUCCESS && add_to_cache && file->path != NULL) + error = git_hashtable_insert(cache->files, file->path, file); + } + +cleanup: + git_buf_free(&path); + return error; +} + + +static int collect_attr_files( + git_repository *repo, const char *path, git_vector *files) +{ + int error = GIT_SUCCESS; + git_buf dir = GIT_BUF_INIT; + git_config *cfg; + const char *workdir = git_repository_workdir(repo); + + if ((error = git_vector_init(files, 4, NULL)) < GIT_SUCCESS) + goto cleanup; + + if ((error = git_path_prettify(&dir, path, workdir)) < GIT_SUCCESS) + goto cleanup; + + if (git_futils_isdir(dir.ptr) != GIT_SUCCESS) { + git_path_dirname_r(&dir, dir.ptr); + git_path_to_dir(&dir); + if ((error = git_buf_lasterror(&dir)) < GIT_SUCCESS) + goto cleanup; + } + + /* in precendence order highest to lowest: + * - $GIT_DIR/info/attributes + * - path components with .gitattributes + * - config core.attributesfile + * - $GIT_PREFIX/etc/gitattributes + */ + + error = push_attrs(repo, files, repo->path_repository, GIT_ATTR_FILE_INREPO); + if (error < GIT_SUCCESS) + goto cleanup; + + if (workdir && git__prefixcmp(dir.ptr, workdir) == 0) { + ssize_t rootlen = (ssize_t)strlen(workdir); + + do { + error = push_attrs(repo, files, dir.ptr, GIT_ATTR_FILE); + if (error == GIT_SUCCESS) { + git_path_dirname_r(&dir, dir.ptr); + git_path_to_dir(&dir); + error = git_buf_lasterror(&dir); + } + } while (!error && dir.size >= rootlen); + } else { + error = push_attrs(repo, files, dir.ptr, GIT_ATTR_FILE); + } + if (error < GIT_SUCCESS) + goto cleanup; + + if (git_repository_config(&cfg, repo) == GIT_SUCCESS) { + const char *core_attribs = NULL; + git_config_get_string(cfg, "core.attributesfile", &core_attribs); + git_clearerror(); /* don't care if attributesfile is not set */ + if (core_attribs) + error = push_attrs(repo, files, NULL, core_attribs); + git_config_free(cfg); + } + + if (error == GIT_SUCCESS) + error = push_attrs(repo, files, NULL, GIT_ATTR_FILE_SYSTEM); + + cleanup: + if (error < GIT_SUCCESS) { + git__rethrow(error, "Could not get attributes for '%s'", path); + git_vector_free(files); + } + git_buf_free(&dir); + + return error; +} + + +void git_repository__attr_cache_free(git_attr_cache *attrs) +{ + if (attrs && attrs->files) { + git_hashtable_free(attrs->files); + attrs->files = NULL; + } +} diff --git a/src/attr.h b/src/attr.h new file mode 100644 index 000000000..518fb9d3b --- /dev/null +++ b/src/attr.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2009-2011 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_attr_h__ +#define INCLUDE_attr_h__ + +#include "hashtable.h" +#include "attr_file.h" + +/* EXPORT */ +typedef struct { + git_hashtable *files; /* hash path to git_attr_file */ +} git_attr_cache; + +extern void git_repository__attr_cache_free(git_attr_cache *attrs); + +#endif + diff --git a/src/attr_file.c b/src/attr_file.c new file mode 100644 index 000000000..5d159db00 --- /dev/null +++ b/src/attr_file.c @@ -0,0 +1,456 @@ +#include "common.h" +#include "attr_file.h" +#include "filebuf.h" +#include <ctype.h> + +const char *git_attr__true = "[internal]__TRUE__"; +const char *git_attr__false = "[internal]__FALSE__"; + +static int parse_fnmatch(git_attr_fnmatch *spec, const char **base); +static int parse_assigns(git_vector *assigns, const char **base); +static int free_rule(git_attr_rule *rule); +static int sort_by_hash_and_name(const void *a_raw, const void *b_raw); + +int git_attr_file__from_buffer(git_attr_file **out, const char *buffer) +{ + int error = GIT_SUCCESS; + git_attr_file *attrs = NULL; + const char *scan = NULL; + git_attr_rule *rule = NULL; + + *out = NULL; + + attrs = git__calloc(1, sizeof(git_attr_file)); + if (attrs == NULL) + return git__throw(GIT_ENOMEM, "Could not allocate attribute storage"); + + attrs->path = NULL; + + error = git_vector_init(&attrs->rules, 4, NULL); + if (error != GIT_SUCCESS) { + git__rethrow(error, "Could not initialize attribute storage"); + goto cleanup; + } + + scan = buffer; + + while (error == GIT_SUCCESS && *scan) { + /* allocate rule if needed */ + if (!rule && !(rule = git__calloc(1, sizeof(git_attr_rule)))) { + error = GIT_ENOMEM; + break; + } + + /* parse the next "pattern attr attr attr" line */ + if (!(error = parse_fnmatch(&rule->match, &scan)) && + !(error = parse_assigns(&rule->assigns, &scan))) + error = git_vector_insert(&attrs->rules, rule); + + /* if the rule wasn't a pattern, on to the next */ + if (error != GIT_SUCCESS) { + free_rule(rule); /* release anything partially allocated */ + if (error == GIT_ENOTFOUND) + error = GIT_SUCCESS; + } else { + rule = NULL; /* vector now "owns" the rule */ + } + } + +cleanup: + if (error != GIT_SUCCESS) { + git_attr_file__free(attrs); + git__free(attrs); + } else { + *out = attrs; + } + + return error; +} + +int git_attr_file__from_file(git_attr_file **out, const char *path) +{ + int error = GIT_SUCCESS; + git_fbuffer fbuf = GIT_FBUFFER_INIT; + + *out = NULL; + + if ((error = git_futils_readbuffer(&fbuf, path)) < GIT_SUCCESS || + (error = git_attr_file__from_buffer(out, fbuf.data)) < GIT_SUCCESS) + { + git__rethrow(error, "Could not open attribute file '%s'", path); + } else { + /* save path (okay to fail) */ + (*out)->path = git__strdup(path); + } + + git_futils_freebuffer(&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) { + free_rule(rule); + } + + git_vector_free(&file->rules); + + git__free(file->path); + file->path = NULL; +} + +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); + git_clearerror(); /* okay if search failed */ + + if (pos >= 0) { + *value = ((git_attr_assignment *) + git_vector_get(&rule->assigns, pos))->value; + break; + } + } + + return GIT_SUCCESS; +} + + +int git_attr_rule__match_path( + git_attr_rule *rule, + const git_attr_path *path) +{ + int matched = FNM_NOMATCH; + + if (rule->match.directory && !path->is_dir) + return matched; + + if (rule->match.fullpath) + matched = p_fnmatch(rule->match.pattern, path->path, FNM_PATHNAME); + else + matched = p_fnmatch(rule->match.pattern, path->basename, 0); + + if (rule->match.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); + git_clearerror(); /* okay if search failed */ + + return (pos >= 0) ? git_vector_get(&rule->assigns, pos) : NULL; +} + +int git_attr_path__init( + git_attr_path *info, const char *path) +{ + info->path = path; + info->basename = strrchr(path, '/'); + if (info->basename) + info->basename++; + if (!info->basename || !*info->basename) + info->basename = path; + info->is_dir = (git_futils_isdir(path) == GIT_SUCCESS); + return GIT_SUCCESS; +} + + +/* + * 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. + */ +static int parse_fnmatch( + git_attr_fnmatch *spec, + const char **base) +{ + const char *pattern; + const char *scan; + int slash_count; + int error = GIT_SUCCESS; + + assert(base && *base); + + pattern = *base; + + while (isspace(*pattern)) pattern++; + if (!*pattern || *pattern == '#') { + error = GIT_ENOTFOUND; + goto skip_to_eol; + } + + if (*pattern == '!') { + spec->negative = 1; + pattern++; + } else { + spec->negative = 0; + } + + spec->fullpath = 0; + slash_count = 0; + for (scan = pattern; *scan != '\0'; ++scan) { + if (isspace(*scan) && *(scan - 1) != '\\') + break; + + if (*scan == '/') { + spec->fullpath = 1; + slash_count++; + } + } + + *base = scan; + spec->length = scan - pattern; + spec->pattern = git__strndup(pattern, spec->length); + + if (!spec->pattern) { + error = GIT_ENOMEM; + goto skip_to_eol; + } else { + char *from = spec->pattern, *to = spec->pattern; + while (*from) { + if (*from == '\\') { + from++; + spec->length--; + } + *to++ = *from++; + } + *to = '\0'; + } + + if (pattern[spec->length - 1] == '/') { + spec->length--; + spec->pattern[spec->length] = '\0'; + spec->directory = 1; + if (--slash_count <= 0) + spec->fullpath = 0; + } else { + spec->directory = 0; + } + + return GIT_SUCCESS; + +skip_to_eol: + /* skip to end of line */ + while (*pattern && *pattern != '\n') pattern++; + if (*pattern == '\n') pattern++; + *base = pattern; + + return error; +} + +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 int parse_assigns( + git_vector *assigns, + const char **base) +{ + int error = GIT_SUCCESS; + const char *scan = *base; + git_attr_assignment *assign = NULL; + + assert(assigns && !assigns->length); + + 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)); + if (!assign) { + error = GIT_ENOMEM; + break; + } + } + + 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++; + } + assign->name_len = scan - name_start; + if (assign->name_len <= 0) { + /* must have found lone prefix (" - ") or leading = ("=foo") + * or end of buffer -- advance until whitespace and continue + */ + while (*scan && !isspace(*scan)) scan++; + continue; + } + + /* 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); + if (!assign->value) { + error = GIT_ENOMEM; + break; + } else { + assign->is_allocated = 1; + } + } + } + + /* allocate permanent storage for name */ + assign->name = git__strndup(name_start, assign->name_len); + if (!assign->name) { + error = GIT_ENOMEM; + break; + } + + /* insert allocated assign into vector */ + error = git_vector_insert(assigns, assign); + if (error < GIT_SUCCESS) + break; + + /* clear assign since it is now "owned" by the vector */ + assign = NULL; + } + + if (!assigns->length) + error = git__throw(GIT_ENOTFOUND, "No attribute assignments found for rule"); + else { + assigns->_cmp = sort_by_hash_and_name; + git_vector_sort(assigns); + } + + if (assign != NULL) { + git__free(assign->name); + if (assign->is_allocated) + git__free((void *)assign->value); + git__free(assign); + } + + while (*scan && *scan != '\n') scan++; + *base = scan; + + return error; +} + +static int free_rule(git_attr_rule *rule) +{ + unsigned int i; + git_attr_assignment *assign; + + if (!rule) + return GIT_SUCCESS; + + git__free(rule->match.pattern); + rule->match.pattern = NULL; + rule->match.length = 0; + + git_vector_foreach(&rule->assigns, i, assign) { + git__free(assign->name); + assign->name = NULL; + + if (assign->is_allocated) { + git__free((void *)assign->value); + assign->value = NULL; + } + } + + return GIT_SUCCESS; +} diff --git a/src/attr_file.h b/src/attr_file.h new file mode 100644 index 000000000..4774f148c --- /dev/null +++ b/src/attr_file.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2009-2011 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_attr_file_h__ +#define INCLUDE_attr_file_h__ + +#include "git2/attr.h" +#include "vector.h" + +typedef struct { + char *pattern; + size_t length; + int negative; + int directory; + int fullpath; +} git_attr_fnmatch; + +typedef struct { + const char *name; + unsigned long name_hash; +} git_attr_name; + +typedef struct { + char *name; + unsigned long name_hash; + size_t name_len; + const char *value; + int is_allocated; +} git_attr_assignment; + +typedef struct { + git_attr_fnmatch match; + git_vector assigns; /* <git_attr_assignment*> */ +} git_attr_rule; + +typedef struct { + char *path; + git_vector rules; /* <git_attr_rule*> */ +} git_attr_file; + +typedef struct { + const char *path; + const char *basename; + int is_dir; +} git_attr_path; + +/* + * git_attr_file API + */ + +extern int git_attr_file__from_buffer(git_attr_file **out, const char *buf); +extern int git_attr_file__from_file(git_attr_file **out, const char *path); + +extern void git_attr_file__free(git_attr_file *file); + +extern int git_attr_file__lookup_one( + git_attr_file *file, + const git_attr_path *path, + const char *attr, + const char **value); + +/* loop over rules in file from bottom to top */ +#define git_attr_file__foreach_matching_rule(file, path, iter, rule) \ + git_vector_rforeach(&(file)->rules, (iter), (rule)) \ + if (git_attr_rule__match_path((rule), (path)) == GIT_SUCCESS) + +extern unsigned long git_attr_file__name_hash(const char *name); + + +/* + * other utilities + */ + +extern int git_attr_rule__match_path( + git_attr_rule *rule, + const git_attr_path *path); + +extern git_attr_assignment *git_attr_rule__lookup_assignment( + git_attr_rule *rule, const char *name); + +extern int git_attr_path__init( + git_attr_path *info, const char *path); + +#endif diff --git a/src/hashtable.c b/src/hashtable.c index 15d173992..f836f166d 100644 --- a/src/hashtable.c +++ b/src/hashtable.c @@ -241,3 +241,17 @@ int git_hashtable_merge(git_hashtable *self, git_hashtable *other) return insert_nodes(self, other->nodes, other->key_count); } + +/** + * Standard string + */ +uint32_t git_hash__strhash_cb(const void *key, int hash_id) +{ + static uint32_t hash_seeds[GIT_HASHTABLE_HASHES] = { + 2147483647, + 0x5d20bb23, + 0x7daaab3c + }; + + return git__hash(key, strlen((const char *)key), hash_seeds[hash_id]); +} diff --git a/src/hashtable.h b/src/hashtable.h index f0ca3ebd2..485b17aa6 100644 --- a/src/hashtable.h +++ b/src/hashtable.h @@ -76,5 +76,12 @@ GIT_INLINE(int) git_hashtable_insert(git_hashtable *h, const void *key, void *va _node->key = NULL; _node->value = NULL; _self->key_count--;\ } +/* + * If you want a hashtable with standard string keys, you can + * just pass git_hash__strcmp_cb and git_hash__strhash_cb to + * git_hashtable_alloc. + */ +#define git_hash__strcmp_cb git__strcmp_cb +extern uint32_t git_hash__strhash_cb(const void *key, int hash_id); #endif diff --git a/src/refs.c b/src/refs.c index 8c3f700ad..cf76b23be 100644 --- a/src/refs.c +++ b/src/refs.c @@ -31,17 +31,6 @@ struct packref { static const int default_table_size = 32; -static uint32_t reftable_hash(const void *key, int hash_id) -{ - static uint32_t hash_seeds[GIT_HASHTABLE_HASHES] = { - 2147483647, - 0x5d20bb23, - 0x7daaab3c - }; - - return git__hash(key, strlen((const char *)key), hash_seeds[hash_id]); -} - static int reference_read( git_fbuffer *file_content, time_t *mtime, @@ -443,9 +432,7 @@ static int packed_load(git_repository *repo) /* First we make sure we have allocated the hash table */ if (ref_cache->packfile == NULL) { ref_cache->packfile = git_hashtable_alloc( - default_table_size, - reftable_hash, - (git_hash_keyeq_ptr)&git__strcmp_cb); + default_table_size, git_hash__strhash_cb, git_hash__strcmp_cb); if (ref_cache->packfile == NULL) { error = GIT_ENOMEM; diff --git a/src/repository.c b/src/repository.c index 67afa2ee2..e0d4c6387 100644 --- a/src/repository.c +++ b/src/repository.c @@ -59,6 +59,7 @@ void git_repository_free(git_repository *repo) git_cache_free(&repo->objects); git_repository__refcache_free(&repo->references); + git_repository__attr_cache_free(&repo->attrcache); git__free(repo->path_repository); git__free(repo->workdir); diff --git a/src/repository.h b/src/repository.h index c3a9a5c60..5274fc1d0 100644 --- a/src/repository.h +++ b/src/repository.h @@ -19,6 +19,7 @@ #include "refs.h" #include "buffer.h" #include "odb.h" +#include "attr.h" #define DOT_GIT ".git" #define GIT_DIR DOT_GIT "/" @@ -38,6 +39,7 @@ struct git_repository { git_cache objects; git_refcache references; + git_attr_cache attrcache; char *path_repository; char *workdir; diff --git a/src/util.h b/src/util.h index 4b1104b7b..be978a6a5 100644 --- a/src/util.h +++ b/src/util.h @@ -109,6 +109,7 @@ extern void **git__bsearch(const void *key, void **base, size_t nmemb, int (*compar)(const void *, const void *)); extern int git__strcmp_cb(const void *a, const void *b); +extern uint32_t git__strhash_cb(const void *key, int hash_id); typedef struct { short refcount; diff --git a/src/vector.c b/src/vector.c index 123aae8e6..e745d77dd 100644 --- a/src/vector.c +++ b/src/vector.c @@ -29,7 +29,12 @@ static int resize_vector(git_vector *v) void git_vector_free(git_vector *v) { assert(v); + git__free(v->contents); + v->contents = NULL; + + v->length = 0; + v->_alloc_size = 0; } int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp) diff --git a/src/vector.h b/src/vector.h index 08f5a501c..4c053e6ae 100644 --- a/src/vector.h +++ b/src/vector.h @@ -19,6 +19,8 @@ typedef struct git_vector { int sorted; } git_vector; +#define GIT_VECTOR_INIT {0} + int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp); void git_vector_free(git_vector *v); void git_vector_clear(git_vector *v); @@ -39,6 +41,9 @@ GIT_INLINE(void *) git_vector_get(git_vector *v, unsigned int position) #define git_vector_foreach(v, iter, elem) \ for ((iter) = 0; (iter) < (v)->length && ((elem) = (v)->contents[(iter)], 1); (iter)++ ) +#define git_vector_rforeach(v, iter, elem) \ + for ((iter) = (v)->length; (iter) > 0 && ((elem) = (v)->contents[(iter)-1], 1); (iter)-- ) + int git_vector_insert(git_vector *v, void *element); int git_vector_remove(git_vector *v, unsigned int idx); void git_vector_uniq(git_vector *v); |
