diff --git a/include/git2/attr.h b/include/git2/attr.h new file mode 100644 index 000000000..f4c5975a6 --- /dev/null +++ b/include/git2/attr.h @@ -0,0 +1,80 @@ +/* + * 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_git_attr_h__ +#define INCLUDE_git_attr_h__ + +#include "common.h" +#include "types.h" + +/** + * @file git2/attr.h + * @brief Git attribute management routines + * @defgroup git_attr Git attribute management routines + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +#define GIT_ATTR_TRUE git_attr__true +#define GIT_ATTR_FALSE git_attr__false +#define GIT_ATTR_UNSPECIFIED NULL + +GIT_EXTERN(const char *)git_attr__true; +GIT_EXTERN(const char *)git_attr__false; + + +/** + * Lookup attribute for path returning string caller must free + */ +GIT_EXTERN(int) git_attr_get( + git_repository *repo, const char *path, const char *name, + const char **value); + +/** + * Lookup list of attributes for path, populating array of strings + */ +GIT_EXTERN(int) git_attr_get_many( + git_repository *repo, const char *path, + size_t num_attr, const char **names, + const char **values); + +/** + * Perform an operation on each attribute of a path. + */ +GIT_EXTERN(int) git_attr_foreach( + git_repository *repo, const char *path, + int (*callback)(const char *name, const char *value, void *payload), + void *payload); + +/** + * Flush the gitattributes cache. + * + * Call this if you have reason to believe that the attributes files + * on disk no longer match the cached contents of memory. + */ +GIT_EXTERN(void) git_attr_cache_flush( + git_repository *repo); + +/** + * Add a macro definition. + * + * Macros will automatically be loaded from the top level .gitattributes + * file of the repository (plus the build-in "binary" macro). This + * function allows you to add others. For example, to add the default + * macro, you would call: + * + * git_attr_add_macro(repo, "binary", "-diff -crlf"); + */ +GIT_EXTERN(int) git_attr_add_macro( + git_repository *repo, + const char *name, + const char *values); + +/** @} */ +GIT_END_DECL +#endif + 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 + +#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 + +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_rule; + +typedef struct { + char *path; /* cache the path this was loaded from */ + git_vector rules; /* vector of */ +} 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_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; + int lim, cmp; + void **part, **base = array; - 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; + 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 diff --git a/tests-clay/attr/file.c b/tests-clay/attr/file.c new file mode 100644 index 000000000..d9e2d5701 --- /dev/null +++ b/tests-clay/attr/file.c @@ -0,0 +1,223 @@ +#include "clay_libgit2.h" +#include "attr_file.h" + +#define get_rule(X) ((git_attr_rule *)git_vector_get(&file->rules,(X))) +#define get_assign(R,Y) ((git_attr_assignment *)git_vector_get(&(R)->assigns,(Y))) + +void test_attr_file__simple_read(void) +{ + git_attr_file *file = NULL; + + cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr0"), &file)); + cl_assert_strequal(cl_fixture("attr/attr0"), file->path); + cl_assert(file->rules.length == 1); + + git_attr_rule *rule = get_rule(0); + cl_assert(rule != NULL); + cl_assert_strequal("*", rule->match.pattern); + cl_assert(rule->match.length == 1); + cl_assert(rule->match.flags == 0); + + cl_assert(rule->assigns.length == 1); + git_attr_assignment *assign = get_assign(rule, 0); + cl_assert(assign != NULL); + cl_assert_strequal("binary", assign->name); + cl_assert(assign->value == GIT_ATTR_TRUE); + cl_assert(!assign->is_allocated); + + git_attr_file__free(file); +} + +void test_attr_file__match_variants(void) +{ + git_attr_file *file = NULL; + git_attr_rule *rule; + git_attr_assignment *assign; + + cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr1"), &file)); + cl_assert_strequal(cl_fixture("attr/attr1"), file->path); + cl_assert(file->rules.length == 10); + + /* let's do a thorough check of this rule, then just verify + * the things that are unique for the later rules + */ + rule = get_rule(0); + cl_assert(rule); + cl_assert_strequal("pat0", rule->match.pattern); + cl_assert(rule->match.length == strlen("pat0")); + cl_assert(rule->match.flags == 0); + cl_assert(rule->assigns.length == 1); + assign = get_assign(rule,0); + cl_assert_strequal("attr0", assign->name); + cl_assert(assign->name_hash == git_attr_file__name_hash(assign->name)); + cl_assert(assign->value == GIT_ATTR_TRUE); + cl_assert(!assign->is_allocated); + + rule = get_rule(1); + cl_assert_strequal("pat1", rule->match.pattern); + cl_assert(rule->match.length == strlen("pat1")); + cl_assert(rule->match.flags == GIT_ATTR_FNMATCH_NEGATIVE); + + rule = get_rule(2); + cl_assert_strequal("pat2", rule->match.pattern); + cl_assert(rule->match.length == strlen("pat2")); + cl_assert(rule->match.flags == GIT_ATTR_FNMATCH_DIRECTORY); + + rule = get_rule(3); + cl_assert_strequal("pat3dir/pat3file", rule->match.pattern); + cl_assert(rule->match.flags == GIT_ATTR_FNMATCH_FULLPATH); + + rule = get_rule(4); + cl_assert_strequal("pat4.*", rule->match.pattern); + cl_assert(rule->match.flags == 0); + + rule = get_rule(5); + cl_assert_strequal("*.pat5", rule->match.pattern); + + rule = get_rule(7); + cl_assert_strequal("pat7[a-e]??[xyz]", rule->match.pattern); + cl_assert(rule->assigns.length == 1); + assign = get_assign(rule,0); + cl_assert_strequal("attr7", assign->name); + cl_assert(assign->value == GIT_ATTR_TRUE); + + rule = get_rule(8); + cl_assert_strequal("pat8 with spaces", rule->match.pattern); + cl_assert(rule->match.length == strlen("pat8 with spaces")); + cl_assert(rule->match.flags == 0); + + rule = get_rule(9); + cl_assert_strequal("pat9", rule->match.pattern); + + git_attr_file__free(file); +} + +static void check_one_assign( + git_attr_file *file, + int rule_idx, + int assign_idx, + const char *pattern, + const char *name, + const char *value, + int is_allocated) +{ + git_attr_rule *rule = get_rule(rule_idx); + git_attr_assignment *assign = get_assign(rule, assign_idx); + + cl_assert_strequal(pattern, rule->match.pattern); + cl_assert(rule->assigns.length == 1); + cl_assert_strequal(name, assign->name); + cl_assert(assign->name_hash == git_attr_file__name_hash(assign->name)); + cl_assert(assign->is_allocated == is_allocated); + if (is_allocated) + cl_assert_strequal(value, assign->value); + else + cl_assert(assign->value == value); +} + +void test_attr_file__assign_variants(void) +{ + git_attr_file *file = NULL; + git_attr_rule *rule; + git_attr_assignment *assign; + + cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr2"), &file)); + cl_assert_strequal(cl_fixture("attr/attr2"), file->path); + cl_assert(file->rules.length == 11); + + check_one_assign(file, 0, 0, "pat0", "simple", GIT_ATTR_TRUE, 0); + check_one_assign(file, 1, 0, "pat1", "neg", GIT_ATTR_FALSE, 0); + check_one_assign(file, 2, 0, "*", "notundef", GIT_ATTR_TRUE, 0); + check_one_assign(file, 3, 0, "pat2", "notundef", NULL, 0); + check_one_assign(file, 4, 0, "pat3", "assigned", "test-value", 1); + check_one_assign(file, 5, 0, "pat4", "rule-with-more-chars", "value-with-more-chars", 1); + check_one_assign(file, 6, 0, "pat5", "empty", GIT_ATTR_TRUE, 0); + check_one_assign(file, 7, 0, "pat6", "negempty", GIT_ATTR_FALSE, 0); + + rule = get_rule(8); + cl_assert_strequal("pat7", rule->match.pattern); + cl_assert(rule->assigns.length == 5); + /* assignments will be sorted by hash value, so we have to do + * lookups by search instead of by position + */ + assign = git_attr_rule__lookup_assignment(rule, "multiple"); + cl_assert(assign); + cl_assert_strequal("multiple", assign->name); + cl_assert(assign->value == GIT_ATTR_TRUE); + assign = git_attr_rule__lookup_assignment(rule, "single"); + cl_assert(assign); + cl_assert_strequal("single", assign->name); + cl_assert(assign->value == GIT_ATTR_FALSE); + assign = git_attr_rule__lookup_assignment(rule, "values"); + cl_assert(assign); + cl_assert_strequal("values", assign->name); + cl_assert_strequal("1", assign->value); + assign = git_attr_rule__lookup_assignment(rule, "also"); + cl_assert(assign); + cl_assert_strequal("also", assign->name); + cl_assert_strequal("a-really-long-value/*", assign->value); + assign = git_attr_rule__lookup_assignment(rule, "happy"); + cl_assert(assign); + cl_assert_strequal("happy", assign->name); + cl_assert_strequal("yes!", assign->value); + assign = git_attr_rule__lookup_assignment(rule, "other"); + cl_assert(!assign); + + rule = get_rule(9); + cl_assert_strequal("pat8", rule->match.pattern); + cl_assert(rule->assigns.length == 2); + assign = git_attr_rule__lookup_assignment(rule, "again"); + cl_assert(assign); + cl_assert_strequal("again", assign->name); + cl_assert(assign->value == GIT_ATTR_TRUE); + assign = git_attr_rule__lookup_assignment(rule, "another"); + cl_assert(assign); + cl_assert_strequal("another", assign->name); + cl_assert_strequal("12321", assign->value); + + check_one_assign(file, 10, 0, "pat9", "at-eof", GIT_ATTR_FALSE, 0); + + git_attr_file__free(file); +} + +void test_attr_file__check_attr_examples(void) +{ + git_attr_file *file = NULL; + git_attr_rule *rule; + git_attr_assignment *assign; + + cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr3"), &file)); + cl_assert_strequal(cl_fixture("attr/attr3"), file->path); + cl_assert(file->rules.length == 3); + + rule = get_rule(0); + cl_assert_strequal("*.java", rule->match.pattern); + cl_assert(rule->assigns.length == 3); + assign = git_attr_rule__lookup_assignment(rule, "diff"); + cl_assert_strequal("diff", assign->name); + cl_assert_strequal("java", assign->value); + assign = git_attr_rule__lookup_assignment(rule, "crlf"); + cl_assert_strequal("crlf", assign->name); + cl_assert(GIT_ATTR_FALSE == assign->value); + assign = git_attr_rule__lookup_assignment(rule, "myAttr"); + cl_assert_strequal("myAttr", assign->name); + cl_assert(GIT_ATTR_TRUE == assign->value); + assign = git_attr_rule__lookup_assignment(rule, "missing"); + cl_assert(assign == NULL); + + rule = get_rule(1); + cl_assert_strequal("NoMyAttr.java", rule->match.pattern); + cl_assert(rule->assigns.length == 1); + assign = get_assign(rule, 0); + cl_assert_strequal("myAttr", assign->name); + cl_assert(assign->value == NULL); + + rule = get_rule(2); + cl_assert_strequal("README", rule->match.pattern); + cl_assert(rule->assigns.length == 1); + assign = get_assign(rule, 0); + cl_assert_strequal("caveat", assign->name); + cl_assert_strequal("unspecified", assign->value); + + git_attr_file__free(file); +} diff --git a/tests-clay/attr/lookup.c b/tests-clay/attr/lookup.c new file mode 100644 index 000000000..fcade5225 --- /dev/null +++ b/tests-clay/attr/lookup.c @@ -0,0 +1,257 @@ +#include "clay_libgit2.h" +#include "attr_file.h" + +void test_attr_lookup__simple(void) +{ + git_attr_file *file = NULL; + git_attr_path path; + const char *value = NULL; + + cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr0"), &file)); + cl_assert_strequal(cl_fixture("attr/attr0"), file->path); + cl_assert(file->rules.length == 1); + + cl_git_pass(git_attr_path__init(&path, "test")); + cl_assert_strequal("test", path.path); + cl_assert_strequal("test", path.basename); + cl_assert(!path.is_dir); + + cl_git_pass(git_attr_file__lookup_one(file,&path,"binary",&value)); + cl_assert(value == GIT_ATTR_TRUE); + + cl_git_pass(git_attr_file__lookup_one(file,&path,"missing",&value)); + cl_assert(!value); + + git_attr_file__free(file); +} + +typedef struct { + const char *path; + const char *attr; + const char *expected; + int use_strcmp; + int force_dir; +} test_case; + +static void run_test_cases(git_attr_file *file, test_case *cases) +{ + git_attr_path path; + const char *value = NULL; + test_case *c; + int error; + + for (c = cases; c->path != NULL; c++) { + cl_git_pass(git_attr_path__init(&path, c->path)); + + if (c->force_dir) + path.is_dir = 1; + + error = git_attr_file__lookup_one(file,&path,c->attr,&value); + if (error != GIT_SUCCESS) + fprintf(stderr, "failure with %s %s %s\n", c->path, c->attr, c->expected); + cl_git_pass(error); + + if (c->use_strcmp) + cl_assert_strequal(c->expected, value); + else + cl_assert(c->expected == value); + } +} + +void test_attr_lookup__match_variants(void) +{ + git_attr_file *file = NULL; + git_attr_path path; + test_case cases[] = { + /* pat0 -> simple match */ + { "pat0", "attr0", GIT_ATTR_TRUE, 0, 0 }, + { "/testing/for/pat0", "attr0", GIT_ATTR_TRUE, 0, 0 }, + { "relative/to/pat0", "attr0", GIT_ATTR_TRUE, 0, 0 }, + { "this-contains-pat0-inside", "attr0", NULL, 0, 0 }, + { "this-aint-right", "attr0", NULL, 0, 0 }, + { "/this/pat0/dont/match", "attr0", NULL, 0, 0 }, + /* negative match */ + { "pat0", "attr1", GIT_ATTR_TRUE, 0, 0 }, + { "pat1", "attr1", NULL, 0, 0 }, + { "/testing/for/pat1", "attr1", NULL, 0, 0 }, + { "/testing/for/pat0", "attr1", GIT_ATTR_TRUE, 0, 0 }, + { "/testing/for/pat1/inside", "attr1", GIT_ATTR_TRUE, 0, 0 }, + { "misc", "attr1", GIT_ATTR_TRUE, 0, 0 }, + /* dir match */ + { "pat2", "attr2", NULL, 0, 0 }, + { "pat2", "attr2", GIT_ATTR_TRUE, 0, 1 }, + { "/testing/for/pat2", "attr2", NULL, 0, 0 }, + { "/testing/for/pat2", "attr2", GIT_ATTR_TRUE, 0, 1 }, + { "/not/pat2/yousee", "attr2", NULL, 0, 0 }, + { "/not/pat2/yousee", "attr2", NULL, 0, 1 }, + /* path match */ + { "pat3file", "attr3", NULL, 0, 0 }, + { "/pat3dir/pat3file", "attr3", NULL, 0, 0 }, + { "pat3dir/pat3file", "attr3", GIT_ATTR_TRUE, 0, 0 }, + /* pattern* match */ + { "pat4.txt", "attr4", GIT_ATTR_TRUE, 0, 0 }, + { "/fun/fun/fun/pat4.c", "attr4", GIT_ATTR_TRUE, 0, 0 }, + { "pat4.", "attr4", GIT_ATTR_TRUE, 0, 0 }, + { "pat4", "attr4", NULL, 0, 0 }, + { "/fun/fun/fun/pat4.dir", "attr4", GIT_ATTR_TRUE, 0, 1 }, + /* *pattern match */ + { "foo.pat5", "attr5", GIT_ATTR_TRUE, 0, 0 }, + { "foo.pat5", "attr5", GIT_ATTR_TRUE, 0, 1 }, + { "/this/is/ok.pat5", "attr5", GIT_ATTR_TRUE, 0, 0 }, + { "/this/is/bad.pat5/yousee.txt", "attr5", NULL, 0, 0 }, + { "foo.pat5", "attr100", NULL, 0, 0 }, + /* glob match with slashes */ + { "foo.pat6", "attr6", NULL, 0, 0 }, + { "pat6/pat6/foobar.pat6", "attr6", GIT_ATTR_TRUE, 0, 0 }, + { "pat6/pat6/.pat6", "attr6", GIT_ATTR_TRUE, 0, 0 }, + { "pat6/pat6/extra/foobar.pat6", "attr6", NULL, 0, 0 }, + { "/prefix/pat6/pat6/foobar.pat6", "attr6", NULL, 0, 0 }, + { "/pat6/pat6/foobar.pat6", "attr6", NULL, 0, 0 }, + /* complex pattern */ + { "pat7a12z", "attr7", GIT_ATTR_TRUE, 0, 0 }, + { "pat7e__x", "attr7", GIT_ATTR_TRUE, 0, 0 }, + { "pat7b/1y", "attr7", NULL, 0, 0 }, /* ? does not match / */ + { "pat7e_x", "attr7", NULL, 0, 0 }, + { "pat7aaaa", "attr7", NULL, 0, 0 }, + { "pat7zzzz", "attr7", NULL, 0, 0 }, + { "/this/can/be/anything/pat7a12z", "attr7", GIT_ATTR_TRUE, 0, 0 }, + { "but/it/still/must/match/pat7aaaa", "attr7", NULL, 0, 0 }, + { "pat7aaay.fail", "attr7", NULL, 0, 0 }, + /* pattern with spaces */ + { "pat8 with spaces", "attr8", GIT_ATTR_TRUE, 0, 0 }, + { "/gotta love/pat8 with spaces", "attr8", GIT_ATTR_TRUE, 0, 0 }, + { "failing pat8 with spaces", "attr8", NULL, 0, 0 }, + { "spaces", "attr8", NULL, 0, 0 }, + /* pattern at eof */ + { "pat9", "attr9", GIT_ATTR_TRUE, 0, 0 }, + { "/eof/pat9", "attr9", GIT_ATTR_TRUE, 0, 0 }, + { "pat", "attr9", NULL, 0, 0 }, + { "at9", "attr9", NULL, 0, 0 }, + { "pat9.fail", "attr9", NULL, 0, 0 }, + /* sentinel at end */ + { NULL, NULL, NULL, 0, 0 } + }; + + cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr1"), &file)); + cl_assert_strequal(cl_fixture("attr/attr1"), file->path); + cl_assert(file->rules.length == 10); + + cl_git_pass(git_attr_path__init(&path, "/testing/for/pat0")); + cl_assert_strequal("pat0", path.basename); + + run_test_cases(file, cases); + + git_attr_file__free(file); +} + +void test_attr_lookup__assign_variants(void) +{ + git_attr_file *file = NULL; + test_case cases[] = { + /* pat0 -> simple assign */ + { "pat0", "simple", GIT_ATTR_TRUE, 0, 0 }, + { "/testing/pat0", "simple", GIT_ATTR_TRUE, 0, 0 }, + { "pat0", "fail", NULL, 0, 0 }, + { "/testing/pat0", "fail", NULL, 0, 0 }, + /* negative assign */ + { "pat1", "neg", GIT_ATTR_FALSE, 0, 0 }, + { "/testing/pat1", "neg", GIT_ATTR_FALSE, 0, 0 }, + { "pat1", "fail", NULL, 0, 0 }, + { "/testing/pat1", "fail", NULL, 0, 0 }, + /* forced undef */ + { "pat1", "notundef", GIT_ATTR_TRUE, 0, 0 }, + { "pat2", "notundef", NULL, 0, 0 }, + { "/lead/in/pat1", "notundef", GIT_ATTR_TRUE, 0, 0 }, + { "/lead/in/pat2", "notundef", NULL, 0, 0 }, + /* assign value */ + { "pat3", "assigned", "test-value", 1, 0 }, + { "pat3", "notassigned", NULL, 0, 0 }, + /* assign value */ + { "pat4", "rule-with-more-chars", "value-with-more-chars", 1, 0 }, + { "pat4", "notassigned-rule-with-more-chars", NULL, 0, 0 }, + /* empty assignments */ + { "pat5", "empty", GIT_ATTR_TRUE, 0, 0 }, + { "pat6", "negempty", GIT_ATTR_FALSE, 0, 0 }, + /* multiple assignment */ + { "pat7", "multiple", GIT_ATTR_TRUE, 0, 0 }, + { "pat7", "single", GIT_ATTR_FALSE, 0, 0 }, + { "pat7", "values", "1", 1, 0 }, + { "pat7", "also", "a-really-long-value/*", 1, 0 }, + { "pat7", "happy", "yes!", 1, 0 }, + { "pat8", "again", GIT_ATTR_TRUE, 0, 0 }, + { "pat8", "another", "12321", 1, 0 }, + /* bad assignment */ + { "patbad0", "simple", NULL, 0, 0 }, + { "patbad0", "notundef", GIT_ATTR_TRUE, 0, 0 }, + { "patbad1", "simple", NULL, 0, 0 }, + /* eof assignment */ + { "pat9", "at-eof", GIT_ATTR_FALSE, 0, 0 }, + /* sentinel at end */ + { NULL, NULL, NULL, 0, 0 } + }; + + cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr2"), &file)); + cl_assert(file->rules.length == 11); + + run_test_cases(file, cases); + + git_attr_file__free(file); +} + +void test_attr_lookup__check_attr_examples(void) +{ + git_attr_file *file = NULL; + test_case cases[] = { + { "foo.java", "diff", "java", 1, 0 }, + { "foo.java", "crlf", GIT_ATTR_FALSE, 0, 0 }, + { "foo.java", "myAttr", GIT_ATTR_TRUE, 0, 0 }, + { "foo.java", "other", NULL, 0, 0 }, + { "/prefix/dir/foo.java", "diff", "java", 1, 0 }, + { "/prefix/dir/foo.java", "crlf", GIT_ATTR_FALSE, 0, 0 }, + { "/prefix/dir/foo.java", "myAttr", GIT_ATTR_TRUE, 0, 0 }, + { "/prefix/dir/foo.java", "other", NULL, 0, 0 }, + { "NoMyAttr.java", "crlf", GIT_ATTR_FALSE, 0, 0 }, + { "NoMyAttr.java", "myAttr", NULL, 0, 0 }, + { "NoMyAttr.java", "other", NULL, 0, 0 }, + { "/prefix/dir/NoMyAttr.java", "crlf", GIT_ATTR_FALSE, 0, 0 }, + { "/prefix/dir/NoMyAttr.java", "myAttr", NULL, 0, 0 }, + { "/prefix/dir/NoMyAttr.java", "other", NULL, 0, 0 }, + { "README", "caveat", "unspecified", 1, 0 }, + { "/specific/path/README", "caveat", "unspecified", 1, 0 }, + { "README", "missing", NULL, 0, 0 }, + { "/specific/path/README", "missing", NULL, 0, 0 }, + /* sentinel at end */ + { NULL, NULL, NULL, 0, 0 } + }; + + cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr3"), &file)); + cl_assert(file->rules.length == 3); + + run_test_cases(file, cases); + + git_attr_file__free(file); +} + +void test_attr_lookup__from_buffer(void) +{ + git_attr_file *file = NULL; + test_case cases[] = { + { "abc", "foo", GIT_ATTR_TRUE, 0, 0 }, + { "abc", "bar", GIT_ATTR_TRUE, 0, 0 }, + { "abc", "baz", GIT_ATTR_TRUE, 0, 0 }, + { "aaa", "foo", GIT_ATTR_TRUE, 0, 0 }, + { "aaa", "bar", NULL, 0, 0 }, + { "aaa", "baz", GIT_ATTR_TRUE, 0, 0 }, + { "qqq", "foo", NULL, 0, 0 }, + { "qqq", "bar", NULL, 0, 0 }, + { "qqq", "baz", GIT_ATTR_TRUE, 0, 0 }, + { NULL, NULL, NULL, 0, 0 } + }; + + cl_git_pass(git_attr_file__from_buffer(NULL, "a* foo\nabc bar\n* baz", &file)); + cl_assert(file->rules.length == 3); + + run_test_cases(file, cases); + + git_attr_file__free(file); +} diff --git a/tests-clay/attr/repo.c b/tests-clay/attr/repo.c new file mode 100644 index 000000000..f87e7bf55 --- /dev/null +++ b/tests-clay/attr/repo.c @@ -0,0 +1,236 @@ +#include "clay_libgit2.h" +#include "fileops.h" +#include "git2/attr.h" + +static git_repository *g_repo = NULL; + +void test_attr_repo__initialize(void) +{ + /* Before each test, instantiate the attr repo from the fixtures and + * rename the .gitted to .git so it is a repo with a working dir. + * Also rename gitattributes to .gitattributes, because it contains + * macro definitions which are only allowed in the root. + */ + cl_fixture_sandbox("attr"); + cl_git_pass(p_rename("attr/.gitted", "attr/.git")); + cl_git_pass(p_rename("attr/gitattributes", "attr/.gitattributes")); + cl_git_pass(git_repository_open(&g_repo, "attr/.git")); +} + +void test_attr_repo__cleanup(void) +{ + git_repository_free(g_repo); + g_repo = NULL; + cl_fixture_cleanup("attr"); +} + +void test_attr_repo__get_one(void) +{ + const char *value; + struct { + const char *file; + const char *attr; + const char *expected; + } test_cases[] = { + { "root_test1", "repoattr", GIT_ATTR_TRUE }, + { "root_test1", "rootattr", GIT_ATTR_TRUE }, + { "root_test1", "missingattr", NULL }, + { "root_test1", "subattr", NULL }, + { "root_test1", "negattr", NULL }, + { "root_test2", "repoattr", GIT_ATTR_TRUE }, + { "root_test2", "rootattr", GIT_ATTR_FALSE }, + { "root_test2", "missingattr", NULL }, + { "root_test2", "multiattr", GIT_ATTR_FALSE }, + { "root_test3", "repoattr", GIT_ATTR_TRUE }, + { "root_test3", "rootattr", NULL }, + { "root_test3", "multiattr", "3" }, + { "root_test3", "multi2", NULL }, + { "subdir/subdir_test1", "repoattr", GIT_ATTR_TRUE }, + { "subdir/subdir_test1", "rootattr", GIT_ATTR_TRUE }, + { "subdir/subdir_test1", "missingattr", NULL }, + { "subdir/subdir_test1", "subattr", "yes" }, + { "subdir/subdir_test1", "negattr", GIT_ATTR_FALSE }, + { "subdir/subdir_test1", "another", NULL }, + { "subdir/subdir_test2.txt", "repoattr", GIT_ATTR_TRUE }, + { "subdir/subdir_test2.txt", "rootattr", GIT_ATTR_TRUE }, + { "subdir/subdir_test2.txt", "missingattr", NULL }, + { "subdir/subdir_test2.txt", "subattr", "yes" }, + { "subdir/subdir_test2.txt", "negattr", GIT_ATTR_FALSE }, + { "subdir/subdir_test2.txt", "another", "one" }, + { NULL, NULL, NULL } + }, *scan; + + for (scan = test_cases; scan->file != NULL; scan++) { + git_buf b = GIT_BUF_INIT; + + git_buf_printf(&b, "%s:%s == expect %s", + scan->file, scan->attr, scan->expected); + + cl_must_pass_( + git_attr_get(g_repo, scan->file, scan->attr, &value) == GIT_SUCCESS, + b.ptr); + + git_buf_printf(&b, ", got %s", value); + + if (scan->expected == NULL || + scan->expected == GIT_ATTR_TRUE || + scan->expected == GIT_ATTR_FALSE) + { + cl_assert_(scan->expected == value, b.ptr); + } else { + cl_assert_strequal(scan->expected, value); + } + + git_buf_free(&b); + } +} + +void test_attr_repo__get_many(void) +{ + const char *names[4] = { "repoattr", "rootattr", "missingattr", "subattr" }; + const char *values[4]; + + cl_git_pass(git_attr_get_many(g_repo, "root_test1", 4, names, values)); + + cl_assert(values[0] == GIT_ATTR_TRUE); + cl_assert(values[1] == GIT_ATTR_TRUE); + cl_assert(values[2] == NULL); + cl_assert(values[3] == NULL); + + cl_git_pass(git_attr_get_many(g_repo, "root_test2", 4, names, values)); + + cl_assert(values[0] == GIT_ATTR_TRUE); + cl_assert(values[1] == GIT_ATTR_FALSE); + cl_assert(values[2] == NULL); + cl_assert(values[3] == NULL); + + cl_git_pass(git_attr_get_many(g_repo, "subdir/subdir_test1", 4, names, values)); + + cl_assert(values[0] == GIT_ATTR_TRUE); + cl_assert(values[1] == GIT_ATTR_TRUE); + cl_assert(values[2] == NULL); + cl_assert_strequal("yes", values[3]); + +} + +static int count_attrs( + const char *GIT_UNUSED(name), + const char *GIT_UNUSED(value), + void *payload) +{ + GIT_UNUSED_ARG(name); + GIT_UNUSED_ARG(value); + + *((int *)payload) += 1; + + return GIT_SUCCESS; +} + +void test_attr_repo__foreach(void) +{ + int count; + + count = 0; + cl_git_pass(git_attr_foreach(g_repo, "root_test1", &count_attrs, &count)); + cl_assert(count == 2); + + count = 0; + cl_git_pass(git_attr_foreach(g_repo, "subdir/subdir_test1", + &count_attrs, &count)); + cl_assert(count == 4); /* repoattr, rootattr, subattr, negattr */ + + count = 0; + cl_git_pass(git_attr_foreach(g_repo, "subdir/subdir_test2.txt", + &count_attrs, &count)); + cl_assert(count == 5); /* repoattr, rootattr, subattr, negattr, another */ +} + +void test_attr_repo__manpage_example(void) +{ + const char *value; + + cl_git_pass(git_attr_get(g_repo, "subdir/abc", "foo", &value)); + cl_assert(value == GIT_ATTR_TRUE); + + cl_git_pass(git_attr_get(g_repo, "subdir/abc", "bar", &value)); + cl_assert(value == NULL); + + cl_git_pass(git_attr_get(g_repo, "subdir/abc", "baz", &value)); + cl_assert(value == GIT_ATTR_FALSE); + + cl_git_pass(git_attr_get(g_repo, "subdir/abc", "merge", &value)); + cl_assert_strequal("filfre", value); + + cl_git_pass(git_attr_get(g_repo, "subdir/abc", "frotz", &value)); + cl_assert(value == NULL); +} + +void test_attr_repo__macros(void) +{ + const char *names[5] = { "rootattr", "binary", "diff", "crlf", "frotz" }; + const char *names2[5] = { "mymacro", "positive", "negative", "rootattr", "another" }; + const char *names3[3] = { "macro2", "multi2", "multi3" }; + const char *values[5]; + + cl_git_pass(git_attr_get_many(g_repo, "binfile", 5, names, values)); + + cl_assert(values[0] == GIT_ATTR_TRUE); + cl_assert(values[1] == GIT_ATTR_TRUE); + cl_assert(values[2] == GIT_ATTR_FALSE); + cl_assert(values[3] == GIT_ATTR_FALSE); + cl_assert(values[4] == NULL); + + cl_git_pass(git_attr_get_many(g_repo, "macro_test", 5, names2, values)); + + cl_assert(values[0] == GIT_ATTR_TRUE); + cl_assert(values[1] == GIT_ATTR_TRUE); + cl_assert(values[2] == GIT_ATTR_FALSE); + cl_assert(values[3] == NULL); + cl_assert_strequal("77", values[4]); + + cl_git_pass(git_attr_get_many(g_repo, "macro_test", 3, names3, values)); + + cl_assert(values[0] == GIT_ATTR_TRUE); + cl_assert(values[1] == GIT_ATTR_FALSE); + cl_assert_strequal("answer", values[2]); +} + +void test_attr_repo__bad_macros(void) +{ + const char *names[6] = { "rootattr", "positive", "negative", + "firstmacro", "secondmacro", "thirdmacro" }; + const char *values[6]; + + cl_git_pass(git_attr_get_many(g_repo, "macro_bad", 6, names, values)); + + /* these three just confirm that the "mymacro" rule ran */ + cl_assert(values[0] == NULL); + cl_assert(values[1] == GIT_ATTR_TRUE); + cl_assert(values[2] == GIT_ATTR_FALSE); + + /* file contains: + * # let's try some malicious macro defs + * [attr]firstmacro -thirdmacro -secondmacro + * [attr]secondmacro firstmacro -firstmacro + * [attr]thirdmacro secondmacro=hahaha -firstmacro + * macro_bad firstmacro secondmacro thirdmacro + * + * firstmacro assignment list ends up with: + * -thirdmacro -secondmacro + * secondmacro assignment list expands "firstmacro" and ends up with: + * -thirdmacro -secondmacro -firstmacro + * thirdmacro assignment don't expand so list ends up with: + * secondmacro="hahaha" + * + * macro_bad assignment list ends up with: + * -thirdmacro -secondmacro firstmacro && + * -thirdmacro -secondmacro -firstmacro secondmacro && + * secondmacro="hahaha" thirdmacro + * + * so summary results should be: + * -firstmacro secondmacro="hahaha" thirdmacro + */ + cl_assert(values[3] == GIT_ATTR_FALSE); + cl_assert_strequal("hahaha", values[4]); + cl_assert(values[5] == GIT_ATTR_TRUE); +} diff --git a/tests-clay/clay.h b/tests-clay/clay.h index c9fe4c166..98c306215 100644 --- a/tests-clay/clay.h +++ b/tests-clay/clay.h @@ -59,6 +59,23 @@ void cl_fixture_cleanup(const char *fixture_name); */ extern void clay_on_init(void); extern void clay_on_shutdown(void); +extern void test_attr_file__assign_variants(void); +extern void test_attr_file__check_attr_examples(void); +extern void test_attr_file__match_variants(void); +extern void test_attr_file__simple_read(void); +extern void test_attr_lookup__assign_variants(void); +extern void test_attr_lookup__check_attr_examples(void); +extern void test_attr_lookup__from_buffer(void); +extern void test_attr_lookup__match_variants(void); +extern void test_attr_lookup__simple(void); +extern void test_attr_repo__bad_macros(void); +extern void test_attr_repo__cleanup(void); +extern void test_attr_repo__foreach(void); +extern void test_attr_repo__get_many(void); +extern void test_attr_repo__get_one(void); +extern void test_attr_repo__initialize(void); +extern void test_attr_repo__macros(void); +extern void test_attr_repo__manpage_example(void); extern void test_buf_basic__printf(void); extern void test_buf_basic__resize(void); extern void test_config_add__cleanup(void); @@ -125,6 +142,9 @@ extern void test_core_strtol__int64(void); extern void test_core_vector__0(void); extern void test_core_vector__1(void); extern void test_core_vector__2(void); +extern void test_core_vector__3(void); +extern void test_core_vector__4(void); +extern void test_core_vector__5(void); extern void test_index_rename__single_file(void); extern void test_network_remotelocal__cleanup(void); extern void test_network_remotelocal__initialize(void); diff --git a/tests-clay/clay_main.c b/tests-clay/clay_main.c index 318e096b6..d9ef970c5 100644 --- a/tests-clay/clay_main.c +++ b/tests-clay/clay_main.c @@ -108,6 +108,27 @@ static int clay_sandbox(void); #define clay_on_suite() /* nop */ /* Autogenerated test data by clay */ +static const struct clay_func _clay_cb_attr_file[] = { + {"assign_variants", &test_attr_file__assign_variants}, + {"check_attr_examples", &test_attr_file__check_attr_examples}, + {"match_variants", &test_attr_file__match_variants}, + {"simple_read", &test_attr_file__simple_read} +}; +static const struct clay_func _clay_cb_attr_lookup[] = { + {"assign_variants", &test_attr_lookup__assign_variants}, + {"check_attr_examples", &test_attr_lookup__check_attr_examples}, + {"from_buffer", &test_attr_lookup__from_buffer}, + {"match_variants", &test_attr_lookup__match_variants}, + {"simple", &test_attr_lookup__simple} +}; +static const struct clay_func _clay_cb_attr_repo[] = { + {"bad_macros", &test_attr_repo__bad_macros}, + {"foreach", &test_attr_repo__foreach}, + {"get_many", &test_attr_repo__get_many}, + {"get_one", &test_attr_repo__get_one}, + {"macros", &test_attr_repo__macros}, + {"manpage_example", &test_attr_repo__manpage_example} +}; static const struct clay_func _clay_cb_buf_basic[] = { {"printf", &test_buf_basic__printf}, {"resize", &test_buf_basic__resize} @@ -194,7 +215,10 @@ static const struct clay_func _clay_cb_core_strtol[] = { static const struct clay_func _clay_cb_core_vector[] = { {"0", &test_core_vector__0}, {"1", &test_core_vector__1}, - {"2", &test_core_vector__2} + {"2", &test_core_vector__2}, + {"3", &test_core_vector__3}, + {"4", &test_core_vector__4}, + {"5", &test_core_vector__5} }; static const struct clay_func _clay_cb_index_rename[] = { {"single_file", &test_index_rename__single_file} @@ -309,6 +333,24 @@ static const struct clay_func _clay_cb_status_worktree[] = { static const struct clay_suite _clay_suites[] = { { + "attr::file", + {NULL, NULL}, + {NULL, NULL}, + _clay_cb_attr_file, 4 + }, + { + "attr::lookup", + {NULL, NULL}, + {NULL, NULL}, + _clay_cb_attr_lookup, 5 + }, + { + "attr::repo", + {"initialize", &test_attr_repo__initialize}, + {"cleanup", &test_attr_repo__cleanup}, + _clay_cb_attr_repo, 6 + }, + { "buf::basic", {NULL, NULL}, {NULL, NULL}, @@ -396,7 +438,7 @@ static const struct clay_suite _clay_suites[] = { "core::vector", {NULL, NULL}, {NULL, NULL}, - _clay_cb_core_vector, 3 + _clay_cb_core_vector, 6 }, { "index::rename", @@ -538,8 +580,8 @@ static const struct clay_suite _clay_suites[] = { } }; -static size_t _clay_suite_count = 38; -static size_t _clay_callback_count = 122; +static size_t _clay_suite_count = 41; +static size_t _clay_callback_count = 140; /* Core test functions */ static void diff --git a/tests-clay/core/vector.c b/tests-clay/core/vector.c index b8a853c60..fdcfb3a77 100644 --- a/tests-clay/core/vector.c +++ b/tests-clay/core/vector.c @@ -64,3 +64,128 @@ void test_core_vector__2(void) } +static int compare_them(const void *a, const void *b) +{ + return (int)((long)a - (long)b); +} + +/* insert_sorted */ +void test_core_vector__3(void) +{ + git_vector x; + long i; + git_vector_init(&x, 1, &compare_them); + + for (i = 0; i < 10; i += 2) { + git_vector_insert_sorted(&x, (void*)(i + 1), NULL); + } + + for (i = 9; i > 0; i -= 2) { + git_vector_insert_sorted(&x, (void*)(i + 1), NULL); + } + + cl_assert(x.length == 10); + for (i = 0; i < 10; ++i) { + cl_assert(git_vector_get(&x, i) == (void*)(i + 1)); + } + + git_vector_free(&x); +} + +/* insert_sorted with duplicates */ +void test_core_vector__4(void) +{ + git_vector x; + long i; + git_vector_init(&x, 1, &compare_them); + + for (i = 0; i < 10; i += 2) { + git_vector_insert_sorted(&x, (void*)(i + 1), NULL); + } + + for (i = 9; i > 0; i -= 2) { + git_vector_insert_sorted(&x, (void*)(i + 1), NULL); + } + + for (i = 0; i < 10; i += 2) { + git_vector_insert_sorted(&x, (void*)(i + 1), NULL); + } + + for (i = 9; i > 0; i -= 2) { + git_vector_insert_sorted(&x, (void*)(i + 1), NULL); + } + + cl_assert(x.length == 20); + for (i = 0; i < 20; ++i) { + cl_assert(git_vector_get(&x, i) == (void*)(i / 2 + 1)); + } + + git_vector_free(&x); +} + +typedef struct { + int content; + int count; +} my_struct; + +static int _struct_count = 0; + +static int compare_structs(const void *a, const void *b) +{ + return ((const my_struct *)a)->content - + ((const my_struct *)b)->content; +} + +static int merge_structs(void **old_raw, void *new) +{ + my_struct *old = *(my_struct **)old_raw; + cl_assert(((my_struct *)old)->content == ((my_struct *)new)->content); + ((my_struct *)old)->count += 1; + git__free(new); + _struct_count--; + return GIT_EEXISTS; +} + +static my_struct *alloc_struct(int value) +{ + my_struct *st = git__malloc(sizeof(my_struct)); + st->content = value; + st->count = 0; + _struct_count++; + return st; +} + +/* insert_sorted with duplicates and special handling */ +void test_core_vector__5(void) +{ + git_vector x; + int i; + + git_vector_init(&x, 1, &compare_structs); + + for (i = 0; i < 10; i += 2) + git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs); + + for (i = 9; i > 0; i -= 2) + git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs); + + cl_assert(x.length == 10); + cl_assert(_struct_count == 10); + + for (i = 0; i < 10; i += 2) + git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs); + + for (i = 9; i > 0; i -= 2) + git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs); + + cl_assert(x.length == 10); + cl_assert(_struct_count == 10); + + for (i = 0; i < 10; ++i) { + cl_assert(((my_struct *)git_vector_get(&x, i))->content == i); + git__free(git_vector_get(&x, i)); + _struct_count--; + } + + git_vector_free(&x); +} diff --git a/tests/resources/attr/.gitted/HEAD b/tests/resources/attr/.gitted/HEAD new file mode 100644 index 000000000..cb089cd89 Binary files /dev/null and b/tests/resources/attr/.gitted/HEAD differ diff --git a/tests/resources/attr/.gitted/config b/tests/resources/attr/.gitted/config new file mode 100644 index 000000000..af107929f Binary files /dev/null and b/tests/resources/attr/.gitted/config differ diff --git a/tests/resources/attr/.gitted/description b/tests/resources/attr/.gitted/description new file mode 100644 index 000000000..498b267a8 Binary files /dev/null and b/tests/resources/attr/.gitted/description differ diff --git a/tests/resources/attr/.gitted/index b/tests/resources/attr/.gitted/index new file mode 100644 index 000000000..c52747e0b Binary files /dev/null and b/tests/resources/attr/.gitted/index differ diff --git a/tests/resources/attr/.gitted/info/attributes b/tests/resources/attr/.gitted/info/attributes new file mode 100644 index 000000000..2e9643a53 Binary files /dev/null and b/tests/resources/attr/.gitted/info/attributes differ diff --git a/tests/resources/attr/.gitted/info/exclude b/tests/resources/attr/.gitted/info/exclude new file mode 100644 index 000000000..a5196d1be Binary files /dev/null and b/tests/resources/attr/.gitted/info/exclude differ diff --git a/tests/resources/attr/.gitted/logs/HEAD b/tests/resources/attr/.gitted/logs/HEAD new file mode 100644 index 000000000..f518a465a Binary files /dev/null and b/tests/resources/attr/.gitted/logs/HEAD differ diff --git a/tests/resources/attr/.gitted/logs/refs/heads/master b/tests/resources/attr/.gitted/logs/refs/heads/master new file mode 100644 index 000000000..f518a465a Binary files /dev/null and b/tests/resources/attr/.gitted/logs/refs/heads/master differ diff --git a/tests/resources/attr/.gitted/objects/29/29de282ce999e95183aedac6451d3384559c4b b/tests/resources/attr/.gitted/objects/29/29de282ce999e95183aedac6451d3384559c4b new file mode 100644 index 000000000..ad84f0854 Binary files /dev/null and b/tests/resources/attr/.gitted/objects/29/29de282ce999e95183aedac6451d3384559c4b differ diff --git a/tests/resources/attr/.gitted/objects/2b/40c5aca159b04ea8d20ffe36cdf8b09369b14a b/tests/resources/attr/.gitted/objects/2b/40c5aca159b04ea8d20ffe36cdf8b09369b14a new file mode 100644 index 000000000..0e2368069 Binary files /dev/null and b/tests/resources/attr/.gitted/objects/2b/40c5aca159b04ea8d20ffe36cdf8b09369b14a differ diff --git a/tests/resources/attr/.gitted/objects/2c/66e14f77196ea763fb1e41612c1aa2bc2d8ed2 b/tests/resources/attr/.gitted/objects/2c/66e14f77196ea763fb1e41612c1aa2bc2d8ed2 new file mode 100644 index 000000000..4b75d50eb Binary files /dev/null and b/tests/resources/attr/.gitted/objects/2c/66e14f77196ea763fb1e41612c1aa2bc2d8ed2 differ diff --git a/tests/resources/attr/.gitted/objects/2d/e7dfe3588f3c7e9ad59e7d50ba90e3329df9d9 b/tests/resources/attr/.gitted/objects/2d/e7dfe3588f3c7e9ad59e7d50ba90e3329df9d9 new file mode 100644 index 000000000..e0fd0468e Binary files /dev/null and b/tests/resources/attr/.gitted/objects/2d/e7dfe3588f3c7e9ad59e7d50ba90e3329df9d9 differ diff --git a/tests/resources/attr/.gitted/objects/3b/74db7ab381105dc0d28f8295a77f6a82989292 b/tests/resources/attr/.gitted/objects/3b/74db7ab381105dc0d28f8295a77f6a82989292 new file mode 100644 index 000000000..e5cef35fa Binary files /dev/null and b/tests/resources/attr/.gitted/objects/3b/74db7ab381105dc0d28f8295a77f6a82989292 differ diff --git a/tests/resources/attr/.gitted/objects/3e/42ffc54a663f9401cc25843d6c0e71a33e4249 b/tests/resources/attr/.gitted/objects/3e/42ffc54a663f9401cc25843d6c0e71a33e4249 new file mode 100644 index 000000000..091d79b14 Binary files /dev/null and b/tests/resources/attr/.gitted/objects/3e/42ffc54a663f9401cc25843d6c0e71a33e4249 differ diff --git a/tests/resources/attr/.gitted/objects/45/141a79a77842c59a63229403220a4e4be74e3d b/tests/resources/attr/.gitted/objects/45/141a79a77842c59a63229403220a4e4be74e3d new file mode 100644 index 000000000..5b58ef024 Binary files /dev/null and b/tests/resources/attr/.gitted/objects/45/141a79a77842c59a63229403220a4e4be74e3d differ diff --git a/tests/resources/attr/.gitted/objects/55/6f8c827b8e4a02ad5cab77dca2bcb3e226b0b3 b/tests/resources/attr/.gitted/objects/55/6f8c827b8e4a02ad5cab77dca2bcb3e226b0b3 new file mode 100644 index 000000000..4bcff1faa Binary files /dev/null and b/tests/resources/attr/.gitted/objects/55/6f8c827b8e4a02ad5cab77dca2bcb3e226b0b3 differ diff --git a/tests/resources/attr/.gitted/objects/58/19a185d77b03325aaf87cafc771db36f6ddca7 b/tests/resources/attr/.gitted/objects/58/19a185d77b03325aaf87cafc771db36f6ddca7 new file mode 100644 index 000000000..fe34eb63a Binary files /dev/null and b/tests/resources/attr/.gitted/objects/58/19a185d77b03325aaf87cafc771db36f6ddca7 differ diff --git a/tests/resources/attr/.gitted/objects/60/5812ab7fe421fdd325a935d35cb06a9234a7d7 b/tests/resources/attr/.gitted/objects/60/5812ab7fe421fdd325a935d35cb06a9234a7d7 new file mode 100644 index 000000000..b0cc51ee6 Binary files /dev/null and b/tests/resources/attr/.gitted/objects/60/5812ab7fe421fdd325a935d35cb06a9234a7d7 differ diff --git a/tests/resources/attr/.gitted/objects/6b/ab5c79cd5140d0f800917f550eb2a3dc32b0da b/tests/resources/attr/.gitted/objects/6b/ab5c79cd5140d0f800917f550eb2a3dc32b0da new file mode 100644 index 000000000..f51e11ccc Binary files /dev/null and b/tests/resources/attr/.gitted/objects/6b/ab5c79cd5140d0f800917f550eb2a3dc32b0da differ diff --git a/tests/resources/attr/.gitted/objects/94/da4faa0a6bfb8ee6ccf7153801a69202b31857 b/tests/resources/attr/.gitted/objects/94/da4faa0a6bfb8ee6ccf7153801a69202b31857 new file mode 100644 index 000000000..a9ddf5d20 Binary files /dev/null and b/tests/resources/attr/.gitted/objects/94/da4faa0a6bfb8ee6ccf7153801a69202b31857 differ diff --git a/tests/resources/attr/.gitted/objects/99/eae476896f4907224978b88e5ecaa6c5bb67a9 b/tests/resources/attr/.gitted/objects/99/eae476896f4907224978b88e5ecaa6c5bb67a9 new file mode 100644 index 000000000..8f5acc70a Binary files /dev/null and b/tests/resources/attr/.gitted/objects/99/eae476896f4907224978b88e5ecaa6c5bb67a9 differ diff --git a/tests/resources/attr/.gitted/objects/9f/b40b6675dde60b5697afceae91b66d908c02d9 b/tests/resources/attr/.gitted/objects/9f/b40b6675dde60b5697afceae91b66d908c02d9 new file mode 100644 index 000000000..7663ad0ad Binary files /dev/null and b/tests/resources/attr/.gitted/objects/9f/b40b6675dde60b5697afceae91b66d908c02d9 differ diff --git a/tests/resources/attr/.gitted/objects/a5/6bbcecaeac760cc26239384d2d4c614e7e4320 b/tests/resources/attr/.gitted/objects/a5/6bbcecaeac760cc26239384d2d4c614e7e4320 new file mode 100644 index 000000000..d898ae9b8 Binary files /dev/null and b/tests/resources/attr/.gitted/objects/a5/6bbcecaeac760cc26239384d2d4c614e7e4320 differ diff --git a/tests/resources/attr/.gitted/objects/a5/d76cad53f66f1312bd995909a5bab3c0820770 b/tests/resources/attr/.gitted/objects/a5/d76cad53f66f1312bd995909a5bab3c0820770 new file mode 100644 index 000000000..cd6a389f9 Binary files /dev/null and b/tests/resources/attr/.gitted/objects/a5/d76cad53f66f1312bd995909a5bab3c0820770 differ diff --git a/tests/resources/attr/.gitted/objects/c0/091889c0c77142b87a1fa5123a6398a61d33e7 b/tests/resources/attr/.gitted/objects/c0/091889c0c77142b87a1fa5123a6398a61d33e7 new file mode 100644 index 000000000..11dc63c79 Binary files /dev/null and b/tests/resources/attr/.gitted/objects/c0/091889c0c77142b87a1fa5123a6398a61d33e7 differ diff --git a/tests/resources/attr/.gitted/objects/c4/85abe35abd4aa6fd83b076a78bbea9e2e7e06c b/tests/resources/attr/.gitted/objects/c4/85abe35abd4aa6fd83b076a78bbea9e2e7e06c new file mode 100644 index 000000000..58569ca0e Binary files /dev/null and b/tests/resources/attr/.gitted/objects/c4/85abe35abd4aa6fd83b076a78bbea9e2e7e06c differ diff --git a/tests/resources/attr/.gitted/objects/c7/aadd770d5907a8475c29e9ee21a27b88bf675d b/tests/resources/attr/.gitted/objects/c7/aadd770d5907a8475c29e9ee21a27b88bf675d new file mode 100644 index 000000000..39aedb7d9 Binary files /dev/null and b/tests/resources/attr/.gitted/objects/c7/aadd770d5907a8475c29e9ee21a27b88bf675d differ diff --git a/tests/resources/attr/.gitted/objects/d5/7da33c16b14326ecb05d19bbea908f5e4c47d9 b/tests/resources/attr/.gitted/objects/d5/7da33c16b14326ecb05d19bbea908f5e4c47d9 new file mode 100644 index 000000000..b96d40c24 Binary files /dev/null and b/tests/resources/attr/.gitted/objects/d5/7da33c16b14326ecb05d19bbea908f5e4c47d9 differ diff --git a/tests/resources/attr/.gitted/objects/d8/00886d9c86731ae5c4a62b0b77c437015e00d2 b/tests/resources/attr/.gitted/objects/d8/00886d9c86731ae5c4a62b0b77c437015e00d2 new file mode 100644 index 000000000..83f3b726d Binary files /dev/null and b/tests/resources/attr/.gitted/objects/d8/00886d9c86731ae5c4a62b0b77c437015e00d2 differ diff --git a/tests/resources/attr/.gitted/objects/dc/cada462d3df8ac6de596fb8c896aba9344f941 b/tests/resources/attr/.gitted/objects/dc/cada462d3df8ac6de596fb8c896aba9344f941 new file mode 100644 index 000000000..ef62f8b9d Binary files /dev/null and b/tests/resources/attr/.gitted/objects/dc/cada462d3df8ac6de596fb8c896aba9344f941 differ diff --git a/tests/resources/attr/.gitted/objects/e5/63cf4758f0d646f1b14b76016aa17fa9e549a4 b/tests/resources/attr/.gitted/objects/e5/63cf4758f0d646f1b14b76016aa17fa9e549a4 new file mode 100644 index 000000000..1bc1f0f0b Binary files /dev/null and b/tests/resources/attr/.gitted/objects/e5/63cf4758f0d646f1b14b76016aa17fa9e549a4 differ diff --git a/tests/resources/attr/.gitted/objects/f2/c6d717cf4a5a3e6b02684155ab07b766982165 b/tests/resources/attr/.gitted/objects/f2/c6d717cf4a5a3e6b02684155ab07b766982165 new file mode 100644 index 000000000..27a25dc86 Binary files /dev/null and b/tests/resources/attr/.gitted/objects/f2/c6d717cf4a5a3e6b02684155ab07b766982165 differ diff --git a/tests/resources/attr/.gitted/objects/fb/5067b1aef3ac1ada4b379dbcb7d17255df7d78 b/tests/resources/attr/.gitted/objects/fb/5067b1aef3ac1ada4b379dbcb7d17255df7d78 new file mode 100644 index 000000000..6c8ff837e Binary files /dev/null and b/tests/resources/attr/.gitted/objects/fb/5067b1aef3ac1ada4b379dbcb7d17255df7d78 differ diff --git a/tests/resources/attr/.gitted/objects/ff/69f8639ce2e6010b3f33a74160aad98b48da2b b/tests/resources/attr/.gitted/objects/ff/69f8639ce2e6010b3f33a74160aad98b48da2b new file mode 100644 index 000000000..b736c0b2b Binary files /dev/null and b/tests/resources/attr/.gitted/objects/ff/69f8639ce2e6010b3f33a74160aad98b48da2b differ diff --git a/tests/resources/attr/.gitted/refs/heads/master b/tests/resources/attr/.gitted/refs/heads/master new file mode 100644 index 000000000..0516af2d2 Binary files /dev/null and b/tests/resources/attr/.gitted/refs/heads/master differ diff --git a/tests/resources/attr/attr0 b/tests/resources/attr/attr0 new file mode 100644 index 000000000..556f8c827 Binary files /dev/null and b/tests/resources/attr/attr0 differ diff --git a/tests/resources/attr/attr1 b/tests/resources/attr/attr1 new file mode 100644 index 000000000..3b74db7ab Binary files /dev/null and b/tests/resources/attr/attr1 differ diff --git a/tests/resources/attr/attr2 b/tests/resources/attr/attr2 new file mode 100644 index 000000000..2c66e14f7 Binary files /dev/null and b/tests/resources/attr/attr2 differ diff --git a/tests/resources/attr/attr3 b/tests/resources/attr/attr3 new file mode 100644 index 000000000..c485abe35 Binary files /dev/null and b/tests/resources/attr/attr3 differ diff --git a/tests/resources/attr/binfile b/tests/resources/attr/binfile new file mode 100644 index 000000000..d800886d9 Binary files /dev/null and b/tests/resources/attr/binfile differ diff --git a/tests/resources/attr/gitattributes b/tests/resources/attr/gitattributes new file mode 100644 index 000000000..2b40c5aca Binary files /dev/null and b/tests/resources/attr/gitattributes differ diff --git a/tests/resources/attr/macro_bad b/tests/resources/attr/macro_bad new file mode 100644 index 000000000..5819a185d Binary files /dev/null and b/tests/resources/attr/macro_bad differ diff --git a/tests/resources/attr/macro_test b/tests/resources/attr/macro_test new file mode 100644 index 000000000..ff69f8639 Binary files /dev/null and b/tests/resources/attr/macro_test differ diff --git a/tests/resources/attr/root_test1 b/tests/resources/attr/root_test1 new file mode 100644 index 000000000..45141a79a Binary files /dev/null and b/tests/resources/attr/root_test1 differ diff --git a/tests/resources/attr/root_test2 b/tests/resources/attr/root_test2 new file mode 100644 index 000000000..45141a79a Binary files /dev/null and b/tests/resources/attr/root_test2 differ diff --git a/tests/resources/attr/root_test3 b/tests/resources/attr/root_test3 new file mode 100644 index 000000000..45141a79a Binary files /dev/null and b/tests/resources/attr/root_test3 differ diff --git a/tests/resources/attr/root_test4.txt b/tests/resources/attr/root_test4.txt new file mode 100644 index 000000000..fb5067b1a Binary files /dev/null and b/tests/resources/attr/root_test4.txt differ diff --git a/tests/resources/attr/subdir/.gitattributes b/tests/resources/attr/subdir/.gitattributes new file mode 100644 index 000000000..99eae4768 Binary files /dev/null and b/tests/resources/attr/subdir/.gitattributes differ diff --git a/tests/resources/attr/subdir/abc b/tests/resources/attr/subdir/abc new file mode 100644 index 000000000..3e42ffc54 Binary files /dev/null and b/tests/resources/attr/subdir/abc differ diff --git a/tests/resources/attr/subdir/subdir_test1 b/tests/resources/attr/subdir/subdir_test1 new file mode 100644 index 000000000..e563cf475 Binary files /dev/null and b/tests/resources/attr/subdir/subdir_test1 differ diff --git a/tests/resources/attr/subdir/subdir_test2.txt b/tests/resources/attr/subdir/subdir_test2.txt new file mode 100644 index 000000000..fb5067b1a Binary files /dev/null and b/tests/resources/attr/subdir/subdir_test2.txt differ diff --git a/tests/resources/attr/subdir2/subdir2_test1 b/tests/resources/attr/subdir2/subdir2_test1 new file mode 100644 index 000000000..dccada462 Binary files /dev/null and b/tests/resources/attr/subdir2/subdir2_test1 differ