Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/libgit2.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRussell Belfer <arrbee@arrbee.com>2011-12-16 22:56:43 +0400
committerRussell Belfer <arrbee@arrbee.com>2011-12-21 04:32:58 +0400
commitee1f0b1aed7798908d9e038b006b66f868613fc3 (patch)
treec60350029b9e4bb14811ac13caf59ad86424f33e /src/attr_file.c
parentbe00b00dd1468f1c625ca3fadc61f2a16edfb8d5 (diff)
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/attr_file.c')
-rw-r--r--src/attr_file.c456
1 files changed, 456 insertions, 0 deletions
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;
+}