summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRussell Belfer <arrbee@arrbee.com>2011-12-16 10:56:43 -0800
committerRussell Belfer <arrbee@arrbee.com>2011-12-20 16:32:58 -0800
commitee1f0b1aed7798908d9e038b006b66f868613fc3 (patch)
treec60350029b9e4bb14811ac13caf59ad86424f33e /src
parentbe00b00dd1468f1c625ca3fadc61f2a16edfb8d5 (diff)
downloadlibgit2-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.c311
-rw-r--r--src/attr.h21
-rw-r--r--src/attr_file.c456
-rw-r--r--src/attr_file.h87
-rw-r--r--src/hashtable.c14
-rw-r--r--src/hashtable.h7
-rw-r--r--src/refs.c15
-rw-r--r--src/repository.c1
-rw-r--r--src/repository.h2
-rw-r--r--src/util.h1
-rw-r--r--src/vector.c5
-rw-r--r--src/vector.h5
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);