diff options
| author | Vicent Martà <tanoku@gmail.com> | 2012-01-02 09:56:48 +0100 |
|---|---|---|
| committer | Vicent Martà <tanoku@gmail.com> | 2012-01-02 09:56:48 +0100 |
| commit | 9191a6d2466d0f3c187a66f65757c95c1c3f772d (patch) | |
| tree | 1fca9c4f9498eb01b3ff91b2ca4425d83c62b393 /src | |
| parent | 9dd4c3e80690ec08eba604e5218d0f4abb8f1a02 (diff) | |
| parent | bd370b14fefdba3844a9bf0bbf87171ca48f49be (diff) | |
| download | libgit2-9191a6d2466d0f3c187a66f65757c95c1c3f772d.tar.gz | |
Merge remote-tracking branch 'arrbee/git-attributes' into development
Conflicts:
tests-clay/clay_main.c
Diffstat (limited to 'src')
| -rw-r--r-- | src/attr.c | 400 | ||||
| -rw-r--r-- | src/attr_file.c | 528 | ||||
| -rw-r--r-- | src/attr_file.h | 110 | ||||
| -rw-r--r-- | src/config.c | 79 | ||||
| -rw-r--r-- | src/config.h | 1 | ||||
| -rw-r--r-- | src/fileops.c | 131 | ||||
| -rw-r--r-- | src/fileops.h | 24 | ||||
| -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.c | 38 | ||||
| -rw-r--r-- | src/util.h | 9 | ||||
| -rw-r--r-- | src/vector.c | 52 | ||||
| -rw-r--r-- | src/vector.h | 8 | ||||
| -rw-r--r-- | src/win32/utf-conv.c | 5 | ||||
| -rw-r--r-- | src/win32/utf-conv.h | 1 |
18 files changed, 1318 insertions, 107 deletions
diff --git a/src/attr.c b/src/attr.c new file mode 100644 index 000000000..f984458d4 --- /dev/null +++ b/src/attr.c @@ -0,0 +1,400 @@ +#include "repository.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 "gitattributes" + +static int collect_attr_files( + git_repository *repo, const char *path, git_vector *files); + +static int attr_cache_init(git_repository *repo); + + +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; +} + + +int git_attr_add_macro( + git_repository *repo, + const char *name, + const char *values) +{ + int error; + git_attr_rule *macro = NULL; + + if ((error = attr_cache_init(repo)) < GIT_SUCCESS) + return error; + + macro = git__calloc(1, sizeof(git_attr_rule)); + if (!macro) + return GIT_ENOMEM; + + macro->match.pattern = git__strdup(name); + if (!macro->match.pattern) { + git__free(macro); + return GIT_ENOMEM; + } + + macro->match.length = strlen(macro->match.pattern); + macro->match.flags = GIT_ATTR_FNMATCH_MACRO; + + error = git_attr_assignment__parse(repo, ¯o->assigns, &values); + + if (error == GIT_SUCCESS) + error = git_attr_cache__insert_macro(repo, macro); + + if (error < GIT_SUCCESS) + git_attr_rule__free(macro); + + 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 ((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(repo, path.ptr, &file); + 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 = attr_cache_init(repo)) < GIT_SUCCESS) + goto cleanup; + + 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 = git_futils_find_system_file(&dir, GIT_ATTR_FILE_SYSTEM); + if (error == GIT_SUCCESS) + error = push_attrs(repo, files, NULL, dir.ptr); + else if (error == GIT_ENOTFOUND) + error = GIT_SUCCESS; + } + + 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; +} + + +static int attr_cache_init(git_repository *repo) +{ + int error = GIT_SUCCESS; + git_attr_cache *cache = &repo->attrcache; + + if (cache->initialized) + return GIT_SUCCESS; + + 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 initialize attribute cache"); + } + + if (cache->macros == NULL) { + cache->macros = git_hashtable_alloc( + 8, git_hash__strhash_cb, git_hash__strcmp_cb); + if (!cache->macros) + return git__throw(GIT_ENOMEM, "Could not initialize attribute cache"); + } + + cache->initialized = 1; + + /* insert default macros */ + error = git_attr_add_macro(repo, "binary", "-diff -crlf"); + + return error; +} + + +void git_attr_cache_flush( + git_repository *repo) +{ + if (!repo) + return; + + if (repo->attrcache.files) { + const void *GIT_UNUSED(name); + git_attr_file *file; + + GIT_HASHTABLE_FOREACH(repo->attrcache.files, name, file, + git_attr_file__free(file)); + + git_hashtable_free(repo->attrcache.files); + repo->attrcache.files = NULL; + } + + if (repo->attrcache.macros) { + const void *GIT_UNUSED(name); + git_attr_rule *rule; + + GIT_HASHTABLE_FOREACH(repo->attrcache.macros, name, rule, + git_attr_rule__free(rule)); + + git_hashtable_free(repo->attrcache.macros); + repo->attrcache.macros = NULL; + } + + repo->attrcache.initialized = 0; +} diff --git a/src/attr_file.c b/src/attr_file.c new file mode 100644 index 000000000..fe8844e2d --- /dev/null +++ b/src/attr_file.c @@ -0,0 +1,528 @@ +#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 git_attr_fnmatch__parse(git_attr_fnmatch *spec, const char **base); +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_cache__insert_macro(git_repository *repo, git_attr_rule *macro) +{ + if (macro->assigns.length == 0) + return git__throw(GIT_EMISSINGOBJDATA, "git attribute macro with no values"); + + return git_hashtable_insert( + repo->attrcache.macros, macro->match.pattern, macro); +} + +int git_attr_file__from_buffer( + git_repository *repo, const char *buffer, git_attr_file **out) +{ + 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 = git_attr_fnmatch__parse(&rule->match, &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 != GIT_SUCCESS) { + git_attr_rule__clear(rule); /* reset rule contents */ + if (error == GIT_ENOTFOUND) + error = GIT_SUCCESS; + } else { + rule = NULL; /* vector now "owns" the rule */ + } + } + +cleanup: + if (error != GIT_SUCCESS) { + git_attr_rule__free(rule); + git_attr_file__free(attrs); + } else { + *out = attrs; + } + + return error; +} + +int git_attr_file__from_file( + git_repository *repo, const char *path, git_attr_file **out) +{ + 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(repo, fbuf.data, out)) < 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) + 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); + 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.flags & GIT_ATTR_FNMATCH_DIRECTORY && !path->is_dir) + return matched; + + if (rule->match.flags & GIT_ATTR_FNMATCH_FULLPATH) + matched = p_fnmatch(rule->match.pattern, path->path, FNM_PATHNAME); + else + matched = p_fnmatch(rule->match.pattern, path->basename, 0); + + 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); + 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 git_attr_fnmatch__parse( + 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; + } + + spec->flags = 0; + + if (*pattern == '[') { + if (strncmp(pattern, "[attr]", 6) == 0) { + spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO; + pattern += 6; + } else { + /* unrecognized meta instructions - skip the line */ + error = GIT_ENOTFOUND; + goto skip_to_eol; + } + } + + if (*pattern == '!') { + spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE; + pattern++; + } + + slash_count = 0; + for (scan = pattern; *scan != '\0'; ++scan) { + if (isspace(*scan) && *(scan - 1) != '\\') + break; + + if (*scan == '/') { + spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH; + 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->flags = spec->flags | GIT_ATTR_FNMATCH_DIRECTORY; + if (--slash_count <= 0) + spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH; + } + + 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 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 = GIT_SUCCESS; + const char *scan = *base; + git_attr_assignment *assign = NULL; + + assert(assigns && !assigns->length); + + assigns->_cmp = sort_by_hash_and_name; + + while (*scan && *scan != '\n' && error == GIT_SUCCESS) { + 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; + } + 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); + if (!assign->name) { + error = GIT_ENOMEM; + break; + } + + /* 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; + } + } + } + + /* 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 == GIT_EEXISTS) + error = GIT_SUCCESS; + else if (error != GIT_SUCCESS) + break; + } + } + } + + /* insert allocated assign into vector */ + error = git_vector_insert_sorted(assigns, assign, &merge_assignments); + if (error == GIT_EEXISTS) + error = GIT_SUCCESS; + else 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"); + + if (assign != NULL) + git_attr_assignment__free(assign); + + while (*scan && *scan != '\n') scan++; + if (*scan == '\n') scan++; + + *base = scan; + + return error; +} + +static void git_attr_rule__clear(git_attr_rule *rule) +{ + unsigned int i; + git_attr_assignment *assign; + + if (!rule) + return; + + git__free(rule->match.pattern); + rule->match.pattern = NULL; + rule->match.length = 0; + + git_vector_foreach(&rule->assigns, i, assign) + GIT_REFCOUNT_DEC(assign, git_attr_assignment__free); + + git_vector_free(&rule->assigns); +} + +void git_attr_rule__free(git_attr_rule *rule) +{ + git_attr_rule__clear(rule); + git__free(rule); +} + diff --git a/src/attr_file.h b/src/attr_file.h new file mode 100644 index 000000000..bed440d61 --- /dev/null +++ b/src/attr_file.h @@ -0,0 +1,110 @@ +/* + * 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" +#include "hashtable.h" + +#define GIT_ATTR_FNMATCH_NEGATIVE (1U << 0) +#define GIT_ATTR_FNMATCH_DIRECTORY (1U << 1) +#define GIT_ATTR_FNMATCH_FULLPATH (1U << 2) +#define GIT_ATTR_FNMATCH_MACRO (1U << 3) + +typedef struct { + char *pattern; + size_t length; + unsigned int flags; +} git_attr_fnmatch; + +typedef struct { + git_refcount unused; + const char *name; + unsigned long name_hash; +} git_attr_name; + +typedef struct { + git_refcount rc; /* for macros */ + char *name; + unsigned long name_hash; + const char *value; + int is_allocated; +} git_attr_assignment; + +typedef struct { + git_attr_fnmatch match; + git_vector assigns; /* vector of <git_attr_assignment*> */ +} git_attr_rule; + +typedef struct { + char *path; /* cache the path this was loaded from */ + git_vector rules; /* vector of <git_attr_rule*> */ +} git_attr_file; + +typedef struct { + const char *path; + const char *basename; + int is_dir; +} git_attr_path; + +typedef struct { + int initialized; + git_hashtable *files; /* hash path to git_attr_file */ + git_hashtable *macros; /* hash name to vector<git_attr_assignment> */ +} git_attr_cache; + +/* + * git_attr_file API + */ + +extern int git_attr_file__from_buffer( + git_repository *repo, const char *buf, git_attr_file **out); +extern int git_attr_file__from_file( + git_repository *repo, const char *path, git_attr_file **out); + +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 void git_attr_rule__free(git_attr_rule *rule); + +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); + +extern int git_attr_assignment__parse( + git_repository *repo, /* needed to expand macros */ + git_vector *assigns, + const char **scan); + +extern int git_attr_cache__insert_macro( + git_repository *repo, git_attr_rule *macro); + +#endif diff --git a/src/config.c b/src/config.c index f8ff05056..1338ef3b1 100644 --- a/src/config.c +++ b/src/config.c @@ -337,6 +337,11 @@ int git_config_get_string(git_config *cfg, const char *name, const char **out) return git__throw(error, "Config value '%s' not found", name); } +int git_config_find_global_r(git_buf *path) +{ + return git_futils_find_global_file(path, GIT_CONFIG_FILENAME); +} + int git_config_find_global(char *global_config_path) { git_buf path = GIT_BUF_INIT; @@ -354,79 +359,9 @@ int git_config_find_global(char *global_config_path) return error; } -int git_config_find_global_r(git_buf *path) +int git_config_find_system_r(git_buf *path) { - int error; - const char *home = getenv("HOME"); - -#ifdef GIT_WIN32 - if (home == NULL) - home = getenv("USERPROFILE"); -#endif - - if (home == NULL) - return git__throw(GIT_EOSERR, "Failed to open global config file. Cannot locate the user's home directory"); - - if ((error = git_buf_joinpath(path, home, GIT_CONFIG_FILENAME)) < GIT_SUCCESS) - return error; - - if (git_futils_exists(path->ptr) < GIT_SUCCESS) { - git_buf_clear(path); - return git__throw(GIT_EOSERR, "Failed to open global config file. The file does not exist"); - } - - return GIT_SUCCESS; -} - - - -#if GIT_WIN32 -static int win32_find_system(git_buf *system_config_path) -{ - const wchar_t *query = L"%PROGRAMFILES%\\Git\\etc\\gitconfig"; - wchar_t *apphome_utf16; - char *apphome_utf8; - DWORD size, ret; - - size = ExpandEnvironmentStringsW(query, NULL, 0); - /* The function gave us the full size of the buffer in chars, including NUL */ - apphome_utf16 = git__malloc(size * sizeof(wchar_t)); - if (apphome_utf16 == NULL) - return GIT_ENOMEM; - - ret = ExpandEnvironmentStringsW(query, apphome_utf16, size); - if (ret != size) - return git__throw(GIT_ERROR, "Failed to expand environment strings"); - - if (_waccess(apphome_utf16, F_OK) < 0) { - git__free(apphome_utf16); - return GIT_ENOTFOUND; - } - - apphome_utf8 = gitwin_from_utf16(apphome_utf16); - git__free(apphome_utf16); - - git_buf_attach(system_config_path, apphome_utf8, 0); - - return GIT_SUCCESS; -} -#endif - -int git_config_find_system_r(git_buf *system_config_path) -{ - if (git_buf_sets(system_config_path, "/etc/gitconfig") < GIT_SUCCESS) - return git_buf_lasterror(system_config_path); - - if (git_futils_exists(system_config_path->ptr) == GIT_SUCCESS) - return GIT_SUCCESS; - - git_buf_clear(system_config_path); - -#if GIT_WIN32 - return win32_find_system(system_config_path); -#else - return GIT_ENOTFOUND; -#endif + return git_futils_find_system_file(path, GIT_CONFIG_FILENAME_SYSTEM); } int git_config_find_system(char *system_config_path) diff --git a/src/config.h b/src/config.h index fc639c6d4..6345b0a5d 100644 --- a/src/config.h +++ b/src/config.h @@ -14,6 +14,7 @@ #define GIT_CONFIG_FILENAME ".gitconfig" #define GIT_CONFIG_FILENAME_INREPO "config" +#define GIT_CONFIG_FILENAME_SYSTEM "gitconfig" #define GIT_CONFIG_FILE_MODE 0666 struct git_config { diff --git a/src/fileops.c b/src/fileops.c index fb2f954d7..5eb7bf6ec 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -403,3 +403,134 @@ int git_futils_contains_file(git_buf *base, const char *file, int append_if_exis return _check_dir_contents(base, file, append_if_exists, &git_futils_isfile); } +int git_futils_find_global_file(git_buf *path, const char *filename) +{ + int error; + const char *home = getenv("HOME"); + +#ifdef GIT_WIN32 + if (home == NULL) + home = getenv("USERPROFILE"); +#endif + + if (home == NULL) + return git__throw(GIT_EOSERR, "Failed to open global %s file. " + "Cannot locate the user's home directory.", filename); + + if ((error = git_buf_joinpath(path, home, filename)) < GIT_SUCCESS) + return error; + + if (git_futils_exists(path->ptr) < GIT_SUCCESS) { + git_buf_clear(path); + return GIT_ENOTFOUND; + } + + return GIT_SUCCESS; +} + +#ifdef GIT_WIN32 +typedef struct { + wchar_t *path; + DWORD len; +} win32_path; + +static const win32_path *win32_system_root(void) +{ + static win32_path s_root = { 0, 0 }; + + if (s_root.path == NULL) { + const wchar_t *root_tmpl = L"%PROGRAMFILES%\\Git\\etc\\"; + + s_root.len = ExpandEnvironmentStringsW(root_tmpl, NULL, 0); + + if (s_root.len <= 0) { + git__throw(GIT_EOSERR, "Failed to expand environment strings"); + return NULL; + } + + s_root.path = git__calloc(s_root.len, sizeof(wchar_t)); + if (s_root.path == NULL) + return NULL; + + if (ExpandEnvironmentStringsW(root_tmpl, s_root.path, s_root.len) != s_root.len) { + git__throw(GIT_EOSERR, "Failed to expand environment strings"); + git__free(s_root.path); + s_root.path = NULL; + return NULL; + } + } + + return &s_root; +} + +static int win32_find_system_file(git_buf *path, const char *filename) +{ + int error = GIT_SUCCESS; + const win32_path *root = win32_system_root(); + size_t len; + wchar_t *file_utf16 = NULL, *scan; + char *file_utf8 = NULL; + + if (!root || !filename || (len = strlen(filename)) == 0) + return GIT_ENOTFOUND; + + /* allocate space for wchar_t path to file */ + file_utf16 = git__calloc(root->len + len + 2, sizeof(wchar_t)); + if (!file_utf16) + return GIT_ENOMEM; + + /* append root + '\\' + filename as wchar_t */ + memcpy(file_utf16, root->path, root->len * sizeof(wchar_t)); + + if (*filename == '/' || *filename == '\\') + filename++; + + if (gitwin_append_utf16(file_utf16 + root->len - 1, filename, len + 1) != + (int)len) { + error = git__throw(GIT_EOSERR, "Failed to build file path"); + goto cleanup; + } + + for (scan = file_utf16; *scan; scan++) + if (*scan == L'/') + *scan = L'\\'; + + /* check access */ + if (_waccess(file_utf16, F_OK) < 0) { + error = GIT_ENOTFOUND; + goto cleanup; + } + + /* convert to utf8 */ + if ((file_utf8 = gitwin_from_utf16(file_utf16)) == NULL) + error = GIT_ENOMEM; + + if (file_utf8) { + git_path_mkposix(file_utf8); + git_buf_attach(path, file_utf8, 0); + } + +cleanup: + git__free(file_utf16); + + return error; +} +#endif + +int git_futils_find_system_file(git_buf *path, const char *filename) +{ + if (git_buf_joinpath(path, "/etc", filename) < GIT_SUCCESS) + return git_buf_lasterror(path); + + if (git_futils_exists(path->ptr) == GIT_SUCCESS) + return GIT_SUCCESS; + + git_buf_clear(path); + +#ifdef GIT_WIN32 + return win32_find_system_file(path, filename); +#else + return GIT_ENOTFOUND; +#endif +} + diff --git a/src/fileops.h b/src/fileops.h index df135d0db..31f3e6a91 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -165,4 +165,28 @@ extern int git_futils_direach( extern int git_futils_cmp_path(const char *name1, int len1, int isdir1, const char *name2, int len2, int isdir2); +/** + * Find a "global" file (i.e. one in a user's home directory). + * + * @param pathbuf buffer to write the full path into + * @param filename name of file to find in the home directory + * @return + * - GIT_SUCCESS if found; + * - GIT_ENOTFOUND if not found; + * - GIT_EOSERR on an unspecified OS related error. + */ +extern int git_futils_find_global_file(git_buf *path, const char *filename); + +/** + * Find a "system" file (i.e. one shared for all users of the system). + * + * @param pathbuf buffer to write the full path into + * @param filename name of file to find in the home directory + * @return + * - GIT_SUCCESS if found; + * - GIT_ENOTFOUND if not found; + * - GIT_EOSERR on an unspecified OS related error. + */ +extern int git_futils_find_system_file(git_buf *path, const char *filename); + #endif /* INCLUDE_fileops_h__ */ 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 4950fd595..2842adab1 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, @@ -445,9 +434,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..a94ecce55 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_attr_cache_flush(repo); git__free(repo->path_repository); git__free(repo->workdir); diff --git a/src/repository.h b/src/repository.h index c3a9a5c60..82052158a 100644 --- a/src/repository.h +++ b/src/repository.h @@ -19,6 +19,7 @@ #include "refs.h" #include "buffer.h" #include "odb.h" +#include "attr_file.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.c b/src/util.c index b3af7ffd8..1ca9d850c 100644 --- a/src/util.c +++ b/src/util.c @@ -348,22 +348,30 @@ uint32_t git__hash(const void *key, int len, uint32_t seed) * Copyright (c) 1990 Regents of the University of California. * All rights reserved. */ -void **git__bsearch(const void *key, void **base, size_t nmemb, int (*compar)(const void *, const void *)) +int git__bsearch( + void **array, + size_t array_len, + const void *key, + int (*compare)(const void *, const void *), + size_t *position) { - int lim, cmp; - void **p; - - for (lim = nmemb; lim != 0; lim >>= 1) { - p = base + (lim >> 1); - cmp = (*compar)(key, *p); - if (cmp > 0) { /* key > p: move right */ - base = p + 1; - lim--; - } else if (cmp == 0) { - return (void **)p; - } /* else move left */ - } - return NULL; + int lim, cmp; + void **part, **base = array; + + for (lim = array_len; lim != 0; lim >>= 1) { + part = base + (lim >> 1); + cmp = (*compare)(key, *part); + if (cmp == 0) { + *position = (part - array); + return GIT_SUCCESS; + } else if (cmp > 0) { /* key > p; take right partition */ + base = part + 1; + lim--; + } /* else take left partition */ + } + + *position = (base - array); + return GIT_ENOTFOUND; } /** diff --git a/src/util.h b/src/util.h index 4b1104b7b..2367bb5f3 100644 --- a/src/util.h +++ b/src/util.h @@ -105,8 +105,13 @@ extern void git__strtolower(char *str); extern int git__fnmatch(const char *pattern, const char *name, int flags); extern void git__tsort(void **dst, size_t size, int (*cmp)(const void *, const void *)); -extern void **git__bsearch(const void *key, void **base, size_t nmemb, - int (*compar)(const void *, const void *)); + +extern int git__bsearch( + void **array, + size_t array_len, + const void *key, + int (*compare)(const void *, const void *), + size_t *position); extern int git__strcmp_cb(const void *a, const void *b); diff --git a/src/vector.c b/src/vector.c index 123aae8e6..593d037d4 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) @@ -69,6 +74,45 @@ int git_vector_insert(git_vector *v, void *element) return GIT_SUCCESS; } +int git_vector_insert_sorted(git_vector *v, void *element, int (*on_dup)(void **old, void *new)) +{ + int error = GIT_SUCCESS; + size_t pos; + + assert(v && v->_cmp); + + if (!v->sorted) + git_vector_sort(v); + + if (v->length >= v->_alloc_size) { + if (resize_vector(v) < 0) + return GIT_ENOMEM; + } + + error = git__bsearch(v->contents, v->length, element, v->_cmp, &pos); + + /* If we found the element and have a duplicate handler callback, + * invoke it. If it returns an error, then cancel insert, otherwise + * proceed with normal insert. + */ + if (error == GIT_SUCCESS && on_dup != NULL) { + error = on_dup(&v->contents[pos], element); + if (error != GIT_SUCCESS) + return error; + } + + /* shift elements to the right */ + if (pos < v->length) { + memmove(v->contents + pos + 1, v->contents + pos, + (v->length - pos) * sizeof(void *)); + } + + v->contents[pos] = element; + v->length++; + + return GIT_SUCCESS; +} + void git_vector_sort(git_vector *v) { assert(v); @@ -82,7 +126,7 @@ void git_vector_sort(git_vector *v) int git_vector_bsearch2(git_vector *v, git_vector_cmp key_lookup, const void *key) { - void **find; + size_t pos; assert(v && key && key_lookup); @@ -92,9 +136,9 @@ int git_vector_bsearch2(git_vector *v, git_vector_cmp key_lookup, const void *ke git_vector_sort(v); - find = git__bsearch(key, v->contents, v->length, key_lookup); - if (find != NULL) - return (int)(find - v->contents); + if (git__bsearch(v->contents, v->length, key, key_lookup, + &pos) == GIT_SUCCESS) + return (int)pos; return git__throw(GIT_ENOTFOUND, "Can't find element"); } diff --git a/src/vector.h b/src/vector.h index 08f5a501c..9ee3c9ed5 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,7 +41,13 @@ 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_insert_sorted(git_vector *v, void *element, + int (*on_dup)(void **old, void *new)); int git_vector_remove(git_vector *v, unsigned int idx); void git_vector_uniq(git_vector *v); + #endif diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c index b41c78f92..b1b838eb7 100644 --- a/src/win32/utf-conv.c +++ b/src/win32/utf-conv.c @@ -57,6 +57,11 @@ wchar_t* gitwin_to_utf16(const char* str) return ret; } +int gitwin_append_utf16(wchar_t *buffer, const char *str, size_t len) +{ + return MultiByteToWideChar(_active_codepage, 0, str, -1, buffer, len); +} + char* gitwin_from_utf16(const wchar_t* str) { char* ret; diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h index da03e3385..bbb5c4f69 100644 --- a/src/win32/utf-conv.h +++ b/src/win32/utf-conv.h @@ -11,6 +11,7 @@ #define INCLUDE_git_utfconv_h__ wchar_t* gitwin_to_utf16(const char* str); +int gitwin_append_utf16(wchar_t *buffer, const char *str, size_t len) char* gitwin_from_utf16(const wchar_t* str); #endif |
