]> git.proxmox.com Git - libgit2.git/blobdiff - src/attr_file.c
Fix core.excludesfile named .gitignore
[libgit2.git] / src / attr_file.c
index 5030ad5de0fdc00ce5fcb010c62892d0d8591df7..ea92336f7559e22f1bfbcdcd2e2d9c70fc2c8ff6 100644 (file)
@@ -1,16 +1,17 @@
 #include "common.h"
 #include "repository.h"
 #include "filebuf.h"
+#include "attr.h"
 #include "git2/blob.h"
 #include "git2/tree.h"
 #include <ctype.h>
 
-const char *git_attr__true  = "[internal]__TRUE__";
-const char *git_attr__false = "[internal]__FALSE__";
-const char *git_attr__unset = "[internal]__UNSET__";
-
 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);
+static bool parse_optimized_patterns(
+       git_attr_fnmatch *spec,
+       git_pool *pool,
+       const char *pattern);
 
 int git_attr_file__new(
        git_attr_file **attrs_ptr,
@@ -38,7 +39,7 @@ int git_attr_file__new(
                attrs->key = git_pool_malloc(attrs->pool, (uint32_t)len + 3);
                GITERR_CHECK_ALLOC(attrs->key);
 
-               attrs->key[0] = '0' + from;
+               attrs->key[0] = '0' + (char)from;
                attrs->key[1] = '#';
                memcpy(&attrs->key[2], path, len);
                attrs->key[len + 2] = '\0';
@@ -57,28 +58,33 @@ fail:
 }
 
 int git_attr_file__parse_buffer(
-       git_repository *repo, const char *buffer, git_attr_file *attrs)
+       git_repository *repo, void *parsedata, const char *buffer, git_attr_file *attrs)
 {
        int error = 0;
-       const char *scan = NULL;
-       char *context = NULL;
+       const char *scan = NULL, *context = NULL;
        git_attr_rule *rule = NULL;
 
+       GIT_UNUSED(parsedata);
+
        assert(buffer && attrs);
 
        scan = buffer;
 
        /* if subdir file path, convert context for file paths */
-       if (attrs->key && git__suffixcmp(attrs->key, "/" GIT_ATTR_FILE) == 0) {
+       if (attrs->key &&
+               git_path_root(attrs->key + 2) < 0 &&
+               git__suffixcmp(attrs->key, "/" GIT_ATTR_FILE) == 0)
                context = attrs->key + 2;
-               context[strlen(context) - strlen(GIT_ATTR_FILE)] = '\0';
-       }
 
        while (!error && *scan) {
                /* allocate rule if needed */
-               if (!rule && !(rule = git__calloc(1, sizeof(git_attr_rule)))) {
-                       error = -1;
-                       break;
+               if (!rule) {
+                       if (!(rule = git__calloc(1, sizeof(git_attr_rule)))) {
+                               error = -1;
+                               break;
+                       }
+                       rule->match.flags = GIT_ATTR_FNMATCH_ALLOWNEG |
+                               GIT_ATTR_FNMATCH_ALLOWMACRO;
                }
 
                /* parse the next "pattern attr attr attr" line */
@@ -108,10 +114,6 @@ int git_attr_file__parse_buffer(
 
        git_attr_rule__free(rule);
 
-       /* restore file path used for context */
-       if (context)
-               context[strlen(context)] = '.'; /* first char of GIT_ATTR_FILE */
-
        return error;
 }
 
@@ -127,7 +129,7 @@ int git_attr_file__new_and_load(
 
        if (!(error = git_futils_readbuffer(&content, path)))
                error = git_attr_file__parse_buffer(
-                       NULL, git_buf_cstr(&content), *attrs_ptr);
+                       NULL, NULL, git_buf_cstr(&content), *attrs_ptr);
 
        git_buf_free(&content);
 
@@ -139,18 +141,23 @@ int git_attr_file__new_and_load(
        return error;
 }
 
-void git_attr_file__free(git_attr_file *file)
+void git_attr_file__clear_rules(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);
+}
+
+void git_attr_file__free(git_attr_file *file)
+{
+       if (!file)
+               return;
+
+       git_attr_file__clear_rules(file);
 
        if (file->pool_is_allocated) {
                git_pool_clear(file->pool);
@@ -178,7 +185,7 @@ int git_attr_file__lookup_one(
        const char *attr,
        const char **value)
 {
-       unsigned int i;
+       size_t i;
        git_attr_name name;
        git_attr_rule *rule;
 
@@ -188,9 +195,9 @@ int git_attr_file__lookup_one(
        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);
+               size_t pos;
 
-               if (pos >= 0) {
+               if (!git_vector_bsearch(&pos, &rule->assigns, &name)) {
                        *value = ((git_attr_assignment *)
                                          git_vector_get(&rule->assigns, pos))->value;
                        break;
@@ -206,16 +213,17 @@ bool git_attr_fnmatch__match(
        const git_attr_path *path)
 {
        int fnm;
+       int icase_flags = (match->flags & GIT_ATTR_FNMATCH_ICASE) ? FNM_CASEFOLD : 0;
 
        if (match->flags & GIT_ATTR_FNMATCH_DIRECTORY && !path->is_dir)
                return false;
 
        if (match->flags & GIT_ATTR_FNMATCH_FULLPATH)
-               fnm = p_fnmatch(match->pattern, path->path, FNM_PATHNAME);
+               fnm = p_fnmatch(match->pattern, path->path, FNM_PATHNAME | icase_flags);
        else if (path->is_dir)
-               fnm = p_fnmatch(match->pattern, path->basename, FNM_LEADING_DIR);
+               fnm = p_fnmatch(match->pattern, path->basename, FNM_LEADING_DIR | icase_flags);
        else
-               fnm = p_fnmatch(match->pattern, path->basename, 0);
+               fnm = p_fnmatch(match->pattern, path->basename, icase_flags);
 
        return (fnm == FNM_NOMATCH) ? false : true;
 }
@@ -236,31 +244,29 @@ bool git_attr_rule__match(
 git_attr_assignment *git_attr_rule__lookup_assignment(
        git_attr_rule *rule, const char *name)
 {
-       int pos;
+       size_t pos;
        git_attr_name key;
        key.name = name;
        key.name_hash = git_attr_file__name_hash(name);
 
-       pos = git_vector_bsearch(&rule->assigns, &key);
+       if (git_vector_bsearch(&pos, &rule->assigns, &key))
+               return NULL;
 
-       return (pos >= 0) ? git_vector_get(&rule->assigns, pos) : NULL;
+       return git_vector_get(&rule->assigns, pos);
 }
 
 int git_attr_path__init(
        git_attr_path *info, const char *path, const char *base)
 {
+       ssize_t root;
+
        /* build full path as best we can */
        git_buf_init(&info->full, 0);
 
-       if (base != NULL && git_path_root(path) < 0) {
-               if (git_buf_joinpath(&info->full, base, path) < 0)
-                       return -1;
-               info->path = info->full.ptr + strlen(base);
-       } else {
-               if (git_buf_sets(&info->full, path) < 0)
-                       return -1;
-               info->path = info->full.ptr;
-       }
+       if (git_path_join_unrooted(&info->full, path, base, &root) < 0)
+               return -1;
+
+       info->path = info->full.ptr + root;
 
        /* remove trailing slashes */
        while (info->full.size > 0) {
@@ -293,7 +299,6 @@ void git_attr_path__free(git_attr_path *info)
        info->basename = NULL;
 }
 
-
 /*
  * From gitattributes(5):
  *
@@ -338,10 +343,16 @@ int git_attr_fnmatch__parse(
        const char **base)
 {
        const char *pattern, *scan;
-       int slash_count;
+       int slash_count, allow_space;
 
        assert(spec && base && *base);
 
+       if (parse_optimized_patterns(spec, pool, *base))
+               return 0;
+
+       spec->flags = (spec->flags & GIT_ATTR_FNMATCH__INCOMING);
+       allow_space = ((spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE) != 0);
+
        pattern = *base;
 
        while (git__isspace(*pattern)) pattern++;
@@ -350,9 +361,7 @@ int git_attr_fnmatch__parse(
                return GIT_ENOTFOUND;
        }
 
-       spec->flags = 0;
-
-       if (*pattern == '[') {
+       if (*pattern == '[' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWMACRO) != 0) {
                if (strncmp(pattern, "[attr]", 6) == 0) {
                        spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO;
                        pattern += 6;
@@ -360,7 +369,7 @@ int git_attr_fnmatch__parse(
                /* else a character range like [a-e]* which is accepted */
        }
 
-       if (*pattern == '!') {
+       if (*pattern == '!' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWNEG) != 0) {
                spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE;
                pattern++;
        }
@@ -368,8 +377,10 @@ int git_attr_fnmatch__parse(
        slash_count = 0;
        for (scan = pattern; *scan != '\0'; ++scan) {
                /* scan until (non-escaped) white space */
-               if (git__isspace(*scan) && *(scan - 1) != '\\')
-                       break;
+               if (git__isspace(*scan) && *(scan - 1) != '\\') {
+                       if (!allow_space || (*scan != ' ' && *scan != '\t'))
+                               break;
+               }
 
                if (*scan == '/') {
                        spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH;
@@ -385,7 +396,8 @@ int git_attr_fnmatch__parse(
 
        *base = scan;
 
-       spec->length = scan - pattern;
+       if ((spec->length = scan - pattern) == 0)
+               return GIT_ENOTFOUND;
 
        if (pattern[spec->length - 1] == '/') {
                spec->length--;
@@ -397,7 +409,10 @@ int git_attr_fnmatch__parse(
        if ((spec->flags & GIT_ATTR_FNMATCH_FULLPATH) != 0 &&
                source != NULL && git_path_root(pattern) < 0)
        {
-               size_t sourcelen = strlen(source);
+        /* use context path minus the trailing filename */
+        char *slash = strrchr(source, '/');
+        size_t sourcelen = slash ? slash - source + 1 : 0;
+
                /* given an unrooted fullpath match from a file inside a repo,
                 * prefix the pattern with the relative directory of the source file
                 */
@@ -418,22 +433,28 @@ int git_attr_fnmatch__parse(
                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);
-               }
+               spec->length = git__unescape(spec->pattern);
        }
 
        return 0;
 }
 
+static bool parse_optimized_patterns(
+       git_attr_fnmatch *spec,
+       git_pool *pool,
+       const char *pattern)
+{
+       if (!pattern[1] && (pattern[0] == '*' || pattern[0] == '.')) {
+               spec->flags = GIT_ATTR_FNMATCH_MATCH_ALL;
+               spec->pattern = git_pool_strndup(pool, pattern, 1);
+               spec->length = 1;
+
+               return true;
+       }
+
+       return false;
+}
+
 static int sort_by_hash_and_name(const void *a_raw, const void *b_raw)
 {
        const git_attr_name *a = a_raw;
@@ -479,7 +500,7 @@ int git_attr_assignment__parse(
 
        assert(assigns && !assigns->length);
 
-       assigns->_cmp = sort_by_hash_and_name;
+       git_vector_set_cmp(assigns, sort_by_hash_and_name);
 
        while (*scan && *scan != '\n') {
                const char *name_start, *value_start;