From ee1f0b1aed7798908d9e038b006b66f868613fc3 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 16 Dec 2011 10:56:43 -0800 Subject: [PATCH 1/4] Add APIs for git attributes This adds APIs for querying git attributes. In addition to the new API in include/git2/attr.h, most of the action is in src/attr_file.[hc] which contains utilities for dealing with a single attributes file, and src/attr.[hc] which contains the implementation of the APIs that merge all applicable attributes files. --- include/git2/attr.h | 56 +++ src/attr.c | 311 ++++++++++++ src/attr.h | 21 + src/attr_file.c | 456 ++++++++++++++++++ src/attr_file.h | 87 ++++ src/hashtable.c | 14 + src/hashtable.h | 7 + src/refs.c | 15 +- src/repository.c | 1 + src/repository.h | 2 + src/util.h | 1 + src/vector.c | 5 + src/vector.h | 5 + tests-clay/attr/file.c | 236 +++++++++ tests-clay/attr/lookup.c | 237 +++++++++ tests-clay/attr/repo.c | 140 ++++++ tests-clay/clay.h | 13 + tests-clay/clay_main.c | 39 +- tests/resources/attr/.gitattributes | Bin 0 -> 54 bytes tests/resources/attr/.gitted/HEAD | Bin 0 -> 23 bytes tests/resources/attr/.gitted/config | Bin 0 -> 111 bytes tests/resources/attr/.gitted/description | Bin 0 -> 73 bytes tests/resources/attr/.gitted/index | Bin 0 -> 1072 bytes tests/resources/attr/.gitted/info/attributes | Bin 0 -> 12 bytes tests/resources/attr/.gitted/info/exclude | Bin 0 -> 240 bytes tests/resources/attr/.gitted/logs/HEAD | Bin 0 -> 170 bytes .../attr/.gitted/logs/refs/heads/master | Bin 0 -> 170 bytes .../29/29de282ce999e95183aedac6451d3384559c4b | Bin 0 -> 58 bytes .../2c/66e14f77196ea763fb1e41612c1aa2bc2d8ed2 | Bin 0 -> 316 bytes .../2d/e7dfe3588f3c7e9ad59e7d50ba90e3329df9d9 | Bin 0 -> 124 bytes .../3b/74db7ab381105dc0d28f8295a77f6a82989292 | Bin 0 -> 276 bytes .../45/141a79a77842c59a63229403220a4e4be74e3d | Bin 0 -> 36 bytes .../55/6f8c827b8e4a02ad5cab77dca2bcb3e226b0b3 | Bin 0 -> 24 bytes .../6b/ab5c79cd5140d0f800917f550eb2a3dc32b0da | Bin 0 -> 135 bytes .../c0/091889c0c77142b87a1fa5123a6398a61d33e7 | Bin 0 -> 290 bytes .../c4/85abe35abd4aa6fd83b076a78bbea9e2e7e06c | Bin 0 -> 129 bytes .../c7/aadd770d5907a8475c29e9ee21a27b88bf675d | Bin 0 -> 60 bytes .../dc/cada462d3df8ac6de596fb8c896aba9344f941 | Bin 0 -> 35 bytes .../e5/63cf4758f0d646f1b14b76016aa17fa9e549a4 | Bin 0 -> 39 bytes .../f2/c6d717cf4a5a3e6b02684155ab07b766982165 | Bin 0 -> 44 bytes .../fb/5067b1aef3ac1ada4b379dbcb7d17255df7d78 | Bin 0 -> 28 bytes .../resources/attr/.gitted/refs/heads/master | Bin 0 -> 41 bytes tests/resources/attr/attr0 | Bin 0 -> 9 bytes tests/resources/attr/attr1 | Bin 0 -> 438 bytes tests/resources/attr/attr2 | Bin 0 -> 552 bytes tests/resources/attr/attr3 | Bin 0 -> 134 bytes tests/resources/attr/root_test1 | Bin 0 -> 20 bytes tests/resources/attr/root_test2 | Bin 0 -> 20 bytes tests/resources/attr/root_test3 | Bin 0 -> 20 bytes tests/resources/attr/root_test4.txt | Bin 0 -> 12 bytes tests/resources/attr/subdir/.gitattributes | Bin 0 -> 49 bytes tests/resources/attr/subdir/subdir_test1 | Bin 0 -> 23 bytes tests/resources/attr/subdir/subdir_test2.txt | Bin 0 -> 12 bytes tests/resources/attr/subdir2/subdir2_test1 | Bin 0 -> 19 bytes 54 files changed, 1630 insertions(+), 16 deletions(-) create mode 100644 include/git2/attr.h create mode 100644 src/attr.c create mode 100644 src/attr.h create mode 100644 src/attr_file.c create mode 100644 src/attr_file.h create mode 100644 tests-clay/attr/file.c create mode 100644 tests-clay/attr/lookup.c create mode 100644 tests-clay/attr/repo.c create mode 100644 tests/resources/attr/.gitattributes create mode 100644 tests/resources/attr/.gitted/HEAD create mode 100644 tests/resources/attr/.gitted/config create mode 100644 tests/resources/attr/.gitted/description create mode 100644 tests/resources/attr/.gitted/index create mode 100644 tests/resources/attr/.gitted/info/attributes create mode 100644 tests/resources/attr/.gitted/info/exclude create mode 100644 tests/resources/attr/.gitted/logs/HEAD create mode 100644 tests/resources/attr/.gitted/logs/refs/heads/master create mode 100644 tests/resources/attr/.gitted/objects/29/29de282ce999e95183aedac6451d3384559c4b create mode 100644 tests/resources/attr/.gitted/objects/2c/66e14f77196ea763fb1e41612c1aa2bc2d8ed2 create mode 100644 tests/resources/attr/.gitted/objects/2d/e7dfe3588f3c7e9ad59e7d50ba90e3329df9d9 create mode 100644 tests/resources/attr/.gitted/objects/3b/74db7ab381105dc0d28f8295a77f6a82989292 create mode 100644 tests/resources/attr/.gitted/objects/45/141a79a77842c59a63229403220a4e4be74e3d create mode 100644 tests/resources/attr/.gitted/objects/55/6f8c827b8e4a02ad5cab77dca2bcb3e226b0b3 create mode 100644 tests/resources/attr/.gitted/objects/6b/ab5c79cd5140d0f800917f550eb2a3dc32b0da create mode 100644 tests/resources/attr/.gitted/objects/c0/091889c0c77142b87a1fa5123a6398a61d33e7 create mode 100644 tests/resources/attr/.gitted/objects/c4/85abe35abd4aa6fd83b076a78bbea9e2e7e06c create mode 100644 tests/resources/attr/.gitted/objects/c7/aadd770d5907a8475c29e9ee21a27b88bf675d create mode 100644 tests/resources/attr/.gitted/objects/dc/cada462d3df8ac6de596fb8c896aba9344f941 create mode 100644 tests/resources/attr/.gitted/objects/e5/63cf4758f0d646f1b14b76016aa17fa9e549a4 create mode 100644 tests/resources/attr/.gitted/objects/f2/c6d717cf4a5a3e6b02684155ab07b766982165 create mode 100644 tests/resources/attr/.gitted/objects/fb/5067b1aef3ac1ada4b379dbcb7d17255df7d78 create mode 100644 tests/resources/attr/.gitted/refs/heads/master create mode 100644 tests/resources/attr/attr0 create mode 100644 tests/resources/attr/attr1 create mode 100644 tests/resources/attr/attr2 create mode 100644 tests/resources/attr/attr3 create mode 100644 tests/resources/attr/root_test1 create mode 100644 tests/resources/attr/root_test2 create mode 100644 tests/resources/attr/root_test3 create mode 100644 tests/resources/attr/root_test4.txt create mode 100644 tests/resources/attr/subdir/.gitattributes create mode 100644 tests/resources/attr/subdir/subdir_test1 create mode 100644 tests/resources/attr/subdir/subdir_test2.txt create mode 100644 tests/resources/attr/subdir2/subdir2_test1 diff --git a/include/git2/attr.h b/include/git2/attr.h new file mode 100644 index 000000000..d585937b7 --- /dev/null +++ b/include/git2/attr.h @@ -0,0 +1,56 @@ +/* + * 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); + +/** @} */ +GIT_END_DECL +#endif + diff --git a/src/attr.c b/src/attr.c new file mode 100644 index 000000000..d8e7095b1 --- /dev/null +++ b/src/attr.c @@ -0,0 +1,311 @@ +#include "attr.h" +#include "buffer.h" +#include "fileops.h" +#include "config.h" +#include + +#define GIT_ATTR_FILE_INREPO "info/attributes" +#define GIT_ATTR_FILE ".gitattributes" +#define GIT_ATTR_FILE_SYSTEM "/etc/gitattributes" +#if GIT_WIN32 +#define GIT_ATTR_FILE_WIN32 L"%PROGRAMFILES%\\Git\\etc\\gitattributes" +#endif + +static int collect_attr_files( + git_repository *repo, const char *path, git_vector *files); + + +int git_attr_get( + git_repository *repo, const char *pathname, + const char *name, const char **value) +{ + int error; + git_attr_path path; + git_vector files = GIT_VECTOR_INIT; + unsigned int i, j; + git_attr_file *file; + git_attr_name attr; + git_attr_rule *rule; + + *value = NULL; + + if ((error = git_attr_path__init(&path, pathname)) < GIT_SUCCESS || + (error = collect_attr_files(repo, pathname, &files)) < GIT_SUCCESS) + return git__rethrow(error, "Could not get attribute for %s", pathname); + + attr.name = name; + attr.name_hash = git_attr_file__name_hash(name); + + git_vector_foreach(&files, i, file) { + + git_attr_file__foreach_matching_rule(file, &path, j, rule) { + int pos = git_vector_bsearch(&rule->assigns, &attr); + git_clearerror(); /* okay if search failed */ + + if (pos >= 0) { + *value = ((git_attr_assignment *)git_vector_get( + &rule->assigns, pos))->value; + goto found; + } + } + } + +found: + git_vector_free(&files); + + return error; +} + + +typedef struct { + git_attr_name name; + git_attr_assignment *found; +} attr_get_many_info; + +int git_attr_get_many( + git_repository *repo, const char *pathname, + size_t num_attr, const char **names, const char **values) +{ + int error; + git_attr_path path; + git_vector files = GIT_VECTOR_INIT; + unsigned int i, j, k; + git_attr_file *file; + git_attr_rule *rule; + attr_get_many_info *info = NULL; + size_t num_found = 0; + + memset(values, 0, sizeof(const char *) * num_attr); + + if ((error = git_attr_path__init(&path, pathname)) < GIT_SUCCESS || + (error = collect_attr_files(repo, pathname, &files)) < GIT_SUCCESS) + return git__rethrow(error, "Could not get attributes for %s", pathname); + + if ((info = git__calloc(num_attr, sizeof(attr_get_many_info))) == NULL) { + git__rethrow(GIT_ENOMEM, "Could not get attributes for %s", pathname); + goto cleanup; + } + + git_vector_foreach(&files, i, file) { + + git_attr_file__foreach_matching_rule(file, &path, j, rule) { + + for (k = 0; k < num_attr; k++) { + int pos; + + if (info[k].found != NULL) /* already found assignment */ + continue; + + if (!info[k].name.name) { + info[k].name.name = names[k]; + info[k].name.name_hash = git_attr_file__name_hash(names[k]); + } + + pos = git_vector_bsearch(&rule->assigns, &info[k].name); + git_clearerror(); /* okay if search failed */ + + if (pos >= 0) { + info[k].found = (git_attr_assignment *) + git_vector_get(&rule->assigns, pos); + values[k] = info[k].found->value; + + if (++num_found == num_attr) + goto cleanup; + } + } + } + } + +cleanup: + git_vector_free(&files); + git__free(info); + + return error; +} + + +int git_attr_foreach( + git_repository *repo, const char *pathname, + int (*callback)(const char *name, const char *value, void *payload), + void *payload) +{ + int error; + git_attr_path path; + git_vector files = GIT_VECTOR_INIT; + unsigned int i, j, k; + git_attr_file *file; + git_attr_rule *rule; + git_attr_assignment *assign; + git_hashtable *seen = NULL; + + if ((error = git_attr_path__init(&path, pathname)) < GIT_SUCCESS || + (error = collect_attr_files(repo, pathname, &files)) < GIT_SUCCESS) + return git__rethrow(error, "Could not get attributes for %s", pathname); + + seen = git_hashtable_alloc(8, git_hash__strhash_cb, git_hash__strcmp_cb); + if (!seen) { + error = GIT_ENOMEM; + goto cleanup; + } + + git_vector_foreach(&files, i, file) { + + git_attr_file__foreach_matching_rule(file, &path, j, rule) { + + git_vector_foreach(&rule->assigns, k, assign) { + /* skip if higher priority assignment was already seen */ + if (git_hashtable_lookup(seen, assign->name)) + continue; + + error = git_hashtable_insert(seen, assign->name, assign); + if (error != GIT_SUCCESS) + goto cleanup; + + error = callback(assign->name, assign->value, payload); + if (error != GIT_SUCCESS) + goto cleanup; + } + } + } + +cleanup: + if (seen) + git_hashtable_free(seen); + git_vector_free(&files); + + if (error != GIT_SUCCESS) + (void)git__rethrow(error, "Could not get attributes for %s", pathname); + + return error; +} + + +/* add git_attr_file to vector of files, loading if needed */ +static int push_attrs( + git_repository *repo, + git_vector *files, + const char *base, + const char *filename) +{ + int error = GIT_SUCCESS; + git_attr_cache *cache = &repo->attrcache; + git_buf path = GIT_BUF_INIT; + git_attr_file *file; + int add_to_cache = 0; + + if (cache->files == NULL) { + cache->files = git_hashtable_alloc( + 8, git_hash__strhash_cb, git_hash__strcmp_cb); + if (!cache->files) + return git__throw(GIT_ENOMEM, "Could not create attribute cache"); + } + + if ((error = git_path_prettify(&path, filename, base)) < GIT_SUCCESS) { + if (error == GIT_EOSERR) + /* file was not found -- ignore error */ + error = GIT_SUCCESS; + goto cleanup; + } + + /* either get attr_file from cache or read from disk */ + file = git_hashtable_lookup(cache->files, path.ptr); + if (file == NULL) { + error = git_attr_file__from_file(&file, path.ptr); + add_to_cache = (error == GIT_SUCCESS); + } + + if (file != NULL) { + /* add file to vector, if we found it */ + error = git_vector_insert(files, file); + + /* add file to cache, if it is new */ + /* do this after above step b/c it is not critical */ + if (error == GIT_SUCCESS && add_to_cache && file->path != NULL) + error = git_hashtable_insert(cache->files, file->path, file); + } + +cleanup: + git_buf_free(&path); + return error; +} + + +static int collect_attr_files( + git_repository *repo, const char *path, git_vector *files) +{ + int error = GIT_SUCCESS; + git_buf dir = GIT_BUF_INIT; + git_config *cfg; + const char *workdir = git_repository_workdir(repo); + + if ((error = git_vector_init(files, 4, NULL)) < GIT_SUCCESS) + goto cleanup; + + if ((error = git_path_prettify(&dir, path, workdir)) < GIT_SUCCESS) + goto cleanup; + + if (git_futils_isdir(dir.ptr) != GIT_SUCCESS) { + git_path_dirname_r(&dir, dir.ptr); + git_path_to_dir(&dir); + if ((error = git_buf_lasterror(&dir)) < GIT_SUCCESS) + goto cleanup; + } + + /* in precendence order highest to lowest: + * - $GIT_DIR/info/attributes + * - path components with .gitattributes + * - config core.attributesfile + * - $GIT_PREFIX/etc/gitattributes + */ + + error = push_attrs(repo, files, repo->path_repository, GIT_ATTR_FILE_INREPO); + if (error < GIT_SUCCESS) + goto cleanup; + + if (workdir && git__prefixcmp(dir.ptr, workdir) == 0) { + ssize_t rootlen = (ssize_t)strlen(workdir); + + do { + error = push_attrs(repo, files, dir.ptr, GIT_ATTR_FILE); + if (error == GIT_SUCCESS) { + git_path_dirname_r(&dir, dir.ptr); + git_path_to_dir(&dir); + error = git_buf_lasterror(&dir); + } + } while (!error && dir.size >= rootlen); + } else { + error = push_attrs(repo, files, dir.ptr, GIT_ATTR_FILE); + } + if (error < GIT_SUCCESS) + goto cleanup; + + if (git_repository_config(&cfg, repo) == GIT_SUCCESS) { + const char *core_attribs = NULL; + git_config_get_string(cfg, "core.attributesfile", &core_attribs); + git_clearerror(); /* don't care if attributesfile is not set */ + if (core_attribs) + error = push_attrs(repo, files, NULL, core_attribs); + git_config_free(cfg); + } + + if (error == GIT_SUCCESS) + error = push_attrs(repo, files, NULL, GIT_ATTR_FILE_SYSTEM); + + cleanup: + if (error < GIT_SUCCESS) { + git__rethrow(error, "Could not get attributes for '%s'", path); + git_vector_free(files); + } + git_buf_free(&dir); + + return error; +} + + +void git_repository__attr_cache_free(git_attr_cache *attrs) +{ + if (attrs && attrs->files) { + git_hashtable_free(attrs->files); + attrs->files = NULL; + } +} diff --git a/src/attr.h b/src/attr.h new file mode 100644 index 000000000..518fb9d3b --- /dev/null +++ b/src/attr.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2009-2011 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_attr_h__ +#define INCLUDE_attr_h__ + +#include "hashtable.h" +#include "attr_file.h" + +/* EXPORT */ +typedef struct { + git_hashtable *files; /* hash path to git_attr_file */ +} git_attr_cache; + +extern void git_repository__attr_cache_free(git_attr_cache *attrs); + +#endif + diff --git a/src/attr_file.c b/src/attr_file.c new file mode 100644 index 000000000..5d159db00 --- /dev/null +++ b/src/attr_file.c @@ -0,0 +1,456 @@ +#include "common.h" +#include "attr_file.h" +#include "filebuf.h" +#include + +const char *git_attr__true = "[internal]__TRUE__"; +const char *git_attr__false = "[internal]__FALSE__"; + +static int parse_fnmatch(git_attr_fnmatch *spec, const char **base); +static int parse_assigns(git_vector *assigns, const char **base); +static int free_rule(git_attr_rule *rule); +static int sort_by_hash_and_name(const void *a_raw, const void *b_raw); + +int git_attr_file__from_buffer(git_attr_file **out, const char *buffer) +{ + int error = GIT_SUCCESS; + git_attr_file *attrs = NULL; + const char *scan = NULL; + git_attr_rule *rule = NULL; + + *out = NULL; + + attrs = git__calloc(1, sizeof(git_attr_file)); + if (attrs == NULL) + return git__throw(GIT_ENOMEM, "Could not allocate attribute storage"); + + attrs->path = NULL; + + error = git_vector_init(&attrs->rules, 4, NULL); + if (error != GIT_SUCCESS) { + git__rethrow(error, "Could not initialize attribute storage"); + goto cleanup; + } + + scan = buffer; + + while (error == GIT_SUCCESS && *scan) { + /* allocate rule if needed */ + if (!rule && !(rule = git__calloc(1, sizeof(git_attr_rule)))) { + error = GIT_ENOMEM; + break; + } + + /* parse the next "pattern attr attr attr" line */ + if (!(error = parse_fnmatch(&rule->match, &scan)) && + !(error = parse_assigns(&rule->assigns, &scan))) + error = git_vector_insert(&attrs->rules, rule); + + /* if the rule wasn't a pattern, on to the next */ + if (error != GIT_SUCCESS) { + free_rule(rule); /* release anything partially allocated */ + if (error == GIT_ENOTFOUND) + error = GIT_SUCCESS; + } else { + rule = NULL; /* vector now "owns" the rule */ + } + } + +cleanup: + if (error != GIT_SUCCESS) { + git_attr_file__free(attrs); + git__free(attrs); + } else { + *out = attrs; + } + + return error; +} + +int git_attr_file__from_file(git_attr_file **out, const char *path) +{ + int error = GIT_SUCCESS; + git_fbuffer fbuf = GIT_FBUFFER_INIT; + + *out = NULL; + + if ((error = git_futils_readbuffer(&fbuf, path)) < GIT_SUCCESS || + (error = git_attr_file__from_buffer(out, fbuf.data)) < GIT_SUCCESS) + { + git__rethrow(error, "Could not open attribute file '%s'", path); + } else { + /* save path (okay to fail) */ + (*out)->path = git__strdup(path); + } + + git_futils_freebuffer(&fbuf); + + return error; +} + +void git_attr_file__free(git_attr_file *file) +{ + unsigned int i; + git_attr_rule *rule; + + if (!file) + return; + + git_vector_foreach(&file->rules, i, rule) { + free_rule(rule); + } + + git_vector_free(&file->rules); + + git__free(file->path); + file->path = NULL; +} + +unsigned long git_attr_file__name_hash(const char *name) +{ + unsigned long h = 5381; + int c; + assert(name); + while ((c = (int)*name++) != 0) + h = ((h << 5) + h) + c; + return h; +} + + +int git_attr_file__lookup_one( + git_attr_file *file, + const git_attr_path *path, + const char *attr, + const char **value) +{ + unsigned int i; + git_attr_name name; + git_attr_rule *rule; + + *value = NULL; + + name.name = attr; + name.name_hash = git_attr_file__name_hash(attr); + + git_attr_file__foreach_matching_rule(file, path, i, rule) { + int pos = git_vector_bsearch(&rule->assigns, &name); + git_clearerror(); /* okay if search failed */ + + if (pos >= 0) { + *value = ((git_attr_assignment *) + git_vector_get(&rule->assigns, pos))->value; + break; + } + } + + return GIT_SUCCESS; +} + + +int git_attr_rule__match_path( + git_attr_rule *rule, + const git_attr_path *path) +{ + int matched = FNM_NOMATCH; + + if (rule->match.directory && !path->is_dir) + return matched; + + if (rule->match.fullpath) + matched = p_fnmatch(rule->match.pattern, path->path, FNM_PATHNAME); + else + matched = p_fnmatch(rule->match.pattern, path->basename, 0); + + if (rule->match.negative) + matched = (matched == GIT_SUCCESS) ? FNM_NOMATCH : GIT_SUCCESS; + + return matched; +} + +git_attr_assignment *git_attr_rule__lookup_assignment( + git_attr_rule *rule, const char *name) +{ + int pos; + git_attr_name key; + key.name = name; + key.name_hash = git_attr_file__name_hash(name); + + pos = git_vector_bsearch(&rule->assigns, &key); + git_clearerror(); /* okay if search failed */ + + return (pos >= 0) ? git_vector_get(&rule->assigns, pos) : NULL; +} + +int git_attr_path__init( + git_attr_path *info, const char *path) +{ + info->path = path; + info->basename = strrchr(path, '/'); + if (info->basename) + info->basename++; + if (!info->basename || !*info->basename) + info->basename = path; + info->is_dir = (git_futils_isdir(path) == GIT_SUCCESS); + return GIT_SUCCESS; +} + + +/* + * From gitattributes(5): + * + * Patterns have the following format: + * + * - A blank line matches no files, so it can serve as a separator for + * readability. + * + * - A line starting with # serves as a comment. + * + * - An optional prefix ! which negates the pattern; any matching file + * excluded by a previous pattern will become included again. If a negated + * pattern matches, this will override lower precedence patterns sources. + * + * - If the pattern ends with a slash, it is removed for the purpose of the + * following description, but it would only find a match with a directory. In + * other words, foo/ will match a directory foo and paths underneath it, but + * will not match a regular file or a symbolic link foo (this is consistent + * with the way how pathspec works in general in git). + * + * - If the pattern does not contain a slash /, git treats it as a shell glob + * pattern and checks for a match against the pathname without leading + * directories. + * + * - Otherwise, git treats the pattern as a shell glob suitable for consumption + * by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will + * not match a / in the pathname. For example, "Documentation/\*.html" matches + * "Documentation/git.html" but not "Documentation/ppc/ppc.html". A leading + * slash matches the beginning of the pathname; for example, "/\*.c" matches + * "cat-file.c" but not "mozilla-sha1/sha1.c". + */ + +/* + * This will return GIT_SUCCESS if the spec was filled out, + * GIT_ENOTFOUND if the fnmatch does not require matching, or + * another error code there was an actual problem. + */ +static int parse_fnmatch( + git_attr_fnmatch *spec, + const char **base) +{ + const char *pattern; + const char *scan; + int slash_count; + int error = GIT_SUCCESS; + + assert(base && *base); + + pattern = *base; + + while (isspace(*pattern)) pattern++; + if (!*pattern || *pattern == '#') { + error = GIT_ENOTFOUND; + goto skip_to_eol; + } + + if (*pattern == '!') { + spec->negative = 1; + pattern++; + } else { + spec->negative = 0; + } + + spec->fullpath = 0; + slash_count = 0; + for (scan = pattern; *scan != '\0'; ++scan) { + if (isspace(*scan) && *(scan - 1) != '\\') + break; + + if (*scan == '/') { + spec->fullpath = 1; + slash_count++; + } + } + + *base = scan; + spec->length = scan - pattern; + spec->pattern = git__strndup(pattern, spec->length); + + if (!spec->pattern) { + error = GIT_ENOMEM; + goto skip_to_eol; + } else { + char *from = spec->pattern, *to = spec->pattern; + while (*from) { + if (*from == '\\') { + from++; + spec->length--; + } + *to++ = *from++; + } + *to = '\0'; + } + + if (pattern[spec->length - 1] == '/') { + spec->length--; + spec->pattern[spec->length] = '\0'; + spec->directory = 1; + if (--slash_count <= 0) + spec->fullpath = 0; + } else { + spec->directory = 0; + } + + return GIT_SUCCESS; + +skip_to_eol: + /* skip to end of line */ + while (*pattern && *pattern != '\n') pattern++; + if (*pattern == '\n') pattern++; + *base = pattern; + + return error; +} + +static int sort_by_hash_and_name(const void *a_raw, const void *b_raw) +{ + const git_attr_name *a = a_raw; + const git_attr_name *b = b_raw; + + if (b->name_hash < a->name_hash) + return 1; + else if (b->name_hash > a->name_hash) + return -1; + else + return strcmp(b->name, a->name); +} + +static int parse_assigns( + git_vector *assigns, + const char **base) +{ + int error = GIT_SUCCESS; + const char *scan = *base; + git_attr_assignment *assign = NULL; + + assert(assigns && !assigns->length); + + while (*scan && *scan != '\n') { + const char *name_start, *value_start; + + /* skip leading blanks */ + while (isspace(*scan) && *scan != '\n') scan++; + + /* allocate assign if needed */ + if (!assign) { + assign = git__calloc(1, sizeof(git_attr_assignment)); + if (!assign) { + error = GIT_ENOMEM; + break; + } + } + + assign->name_hash = 5381; + assign->value = GIT_ATTR_TRUE; + assign->is_allocated = 0; + + /* look for magic name prefixes */ + if (*scan == '-') { + assign->value = GIT_ATTR_FALSE; + scan++; + } else if (*scan == '!') { + assign->value = NULL; /* explicit unspecified state */ + scan++; + } else if (*scan == '#') /* comment rest of line */ + break; + + /* find the name */ + name_start = scan; + while (*scan && !isspace(*scan) && *scan != '=') { + assign->name_hash = + ((assign->name_hash << 5) + assign->name_hash) + *scan; + scan++; + } + assign->name_len = scan - name_start; + if (assign->name_len <= 0) { + /* must have found lone prefix (" - ") or leading = ("=foo") + * or end of buffer -- advance until whitespace and continue + */ + while (*scan && !isspace(*scan)) scan++; + continue; + } + + /* if there is an equals sign, find the value */ + if (*scan == '=') { + for (value_start = ++scan; *scan && !isspace(*scan); ++scan); + + /* if we found a value, allocate permanent storage for it */ + if (scan > value_start) { + assign->value = git__strndup(value_start, scan - value_start); + if (!assign->value) { + error = GIT_ENOMEM; + break; + } else { + assign->is_allocated = 1; + } + } + } + + /* allocate permanent storage for name */ + assign->name = git__strndup(name_start, assign->name_len); + if (!assign->name) { + error = GIT_ENOMEM; + break; + } + + /* insert allocated assign into vector */ + error = git_vector_insert(assigns, assign); + if (error < GIT_SUCCESS) + break; + + /* clear assign since it is now "owned" by the vector */ + assign = NULL; + } + + if (!assigns->length) + error = git__throw(GIT_ENOTFOUND, "No attribute assignments found for rule"); + else { + assigns->_cmp = sort_by_hash_and_name; + git_vector_sort(assigns); + } + + if (assign != NULL) { + git__free(assign->name); + if (assign->is_allocated) + git__free((void *)assign->value); + git__free(assign); + } + + while (*scan && *scan != '\n') scan++; + *base = scan; + + return error; +} + +static int free_rule(git_attr_rule *rule) +{ + unsigned int i; + git_attr_assignment *assign; + + if (!rule) + return GIT_SUCCESS; + + git__free(rule->match.pattern); + rule->match.pattern = NULL; + rule->match.length = 0; + + git_vector_foreach(&rule->assigns, i, assign) { + git__free(assign->name); + assign->name = NULL; + + if (assign->is_allocated) { + git__free((void *)assign->value); + assign->value = NULL; + } + } + + return GIT_SUCCESS; +} diff --git a/src/attr_file.h b/src/attr_file.h new file mode 100644 index 000000000..4774f148c --- /dev/null +++ b/src/attr_file.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2009-2011 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_attr_file_h__ +#define INCLUDE_attr_file_h__ + +#include "git2/attr.h" +#include "vector.h" + +typedef struct { + char *pattern; + size_t length; + int negative; + int directory; + int fullpath; +} git_attr_fnmatch; + +typedef struct { + const char *name; + unsigned long name_hash; +} git_attr_name; + +typedef struct { + char *name; + unsigned long name_hash; + size_t name_len; + const char *value; + int is_allocated; +} git_attr_assignment; + +typedef struct { + git_attr_fnmatch match; + git_vector assigns; /* */ +} git_attr_rule; + +typedef struct { + char *path; + git_vector rules; /* */ +} git_attr_file; + +typedef struct { + const char *path; + const char *basename; + int is_dir; +} git_attr_path; + +/* + * git_attr_file API + */ + +extern int git_attr_file__from_buffer(git_attr_file **out, const char *buf); +extern int git_attr_file__from_file(git_attr_file **out, const char *path); + +extern void git_attr_file__free(git_attr_file *file); + +extern int git_attr_file__lookup_one( + git_attr_file *file, + const git_attr_path *path, + const char *attr, + const char **value); + +/* loop over rules in file from bottom to top */ +#define git_attr_file__foreach_matching_rule(file, path, iter, rule) \ + git_vector_rforeach(&(file)->rules, (iter), (rule)) \ + if (git_attr_rule__match_path((rule), (path)) == GIT_SUCCESS) + +extern unsigned long git_attr_file__name_hash(const char *name); + + +/* + * other utilities + */ + +extern int git_attr_rule__match_path( + git_attr_rule *rule, + const git_attr_path *path); + +extern git_attr_assignment *git_attr_rule__lookup_assignment( + git_attr_rule *rule, const char *name); + +extern int git_attr_path__init( + git_attr_path *info, const char *path); + +#endif diff --git a/src/hashtable.c b/src/hashtable.c index 15d173992..f836f166d 100644 --- a/src/hashtable.c +++ b/src/hashtable.c @@ -241,3 +241,17 @@ int git_hashtable_merge(git_hashtable *self, git_hashtable *other) return insert_nodes(self, other->nodes, other->key_count); } + +/** + * Standard string + */ +uint32_t git_hash__strhash_cb(const void *key, int hash_id) +{ + static uint32_t hash_seeds[GIT_HASHTABLE_HASHES] = { + 2147483647, + 0x5d20bb23, + 0x7daaab3c + }; + + return git__hash(key, strlen((const char *)key), hash_seeds[hash_id]); +} diff --git a/src/hashtable.h b/src/hashtable.h index f0ca3ebd2..485b17aa6 100644 --- a/src/hashtable.h +++ b/src/hashtable.h @@ -76,5 +76,12 @@ GIT_INLINE(int) git_hashtable_insert(git_hashtable *h, const void *key, void *va _node->key = NULL; _node->value = NULL; _self->key_count--;\ } +/* + * If you want a hashtable with standard string keys, you can + * just pass git_hash__strcmp_cb and git_hash__strhash_cb to + * git_hashtable_alloc. + */ +#define git_hash__strcmp_cb git__strcmp_cb +extern uint32_t git_hash__strhash_cb(const void *key, int hash_id); #endif diff --git a/src/refs.c b/src/refs.c index 8c3f700ad..cf76b23be 100644 --- a/src/refs.c +++ b/src/refs.c @@ -31,17 +31,6 @@ struct packref { static const int default_table_size = 32; -static uint32_t reftable_hash(const void *key, int hash_id) -{ - static uint32_t hash_seeds[GIT_HASHTABLE_HASHES] = { - 2147483647, - 0x5d20bb23, - 0x7daaab3c - }; - - return git__hash(key, strlen((const char *)key), hash_seeds[hash_id]); -} - static int reference_read( git_fbuffer *file_content, time_t *mtime, @@ -443,9 +432,7 @@ static int packed_load(git_repository *repo) /* First we make sure we have allocated the hash table */ if (ref_cache->packfile == NULL) { ref_cache->packfile = git_hashtable_alloc( - default_table_size, - reftable_hash, - (git_hash_keyeq_ptr)&git__strcmp_cb); + default_table_size, git_hash__strhash_cb, git_hash__strcmp_cb); if (ref_cache->packfile == NULL) { error = GIT_ENOMEM; diff --git a/src/repository.c b/src/repository.c index 67afa2ee2..e0d4c6387 100644 --- a/src/repository.c +++ b/src/repository.c @@ -59,6 +59,7 @@ void git_repository_free(git_repository *repo) git_cache_free(&repo->objects); git_repository__refcache_free(&repo->references); + git_repository__attr_cache_free(&repo->attrcache); git__free(repo->path_repository); git__free(repo->workdir); diff --git a/src/repository.h b/src/repository.h index c3a9a5c60..5274fc1d0 100644 --- a/src/repository.h +++ b/src/repository.h @@ -19,6 +19,7 @@ #include "refs.h" #include "buffer.h" #include "odb.h" +#include "attr.h" #define DOT_GIT ".git" #define GIT_DIR DOT_GIT "/" @@ -38,6 +39,7 @@ struct git_repository { git_cache objects; git_refcache references; + git_attr_cache attrcache; char *path_repository; char *workdir; diff --git a/src/util.h b/src/util.h index 4b1104b7b..be978a6a5 100644 --- a/src/util.h +++ b/src/util.h @@ -109,6 +109,7 @@ extern void **git__bsearch(const void *key, void **base, size_t nmemb, int (*compar)(const void *, const void *)); extern int git__strcmp_cb(const void *a, const void *b); +extern uint32_t git__strhash_cb(const void *key, int hash_id); typedef struct { short refcount; diff --git a/src/vector.c b/src/vector.c index 123aae8e6..e745d77dd 100644 --- a/src/vector.c +++ b/src/vector.c @@ -29,7 +29,12 @@ static int resize_vector(git_vector *v) void git_vector_free(git_vector *v) { assert(v); + git__free(v->contents); + v->contents = NULL; + + v->length = 0; + v->_alloc_size = 0; } int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp) diff --git a/src/vector.h b/src/vector.h index 08f5a501c..4c053e6ae 100644 --- a/src/vector.h +++ b/src/vector.h @@ -19,6 +19,8 @@ typedef struct git_vector { int sorted; } git_vector; +#define GIT_VECTOR_INIT {0} + int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp); void git_vector_free(git_vector *v); void git_vector_clear(git_vector *v); @@ -39,6 +41,9 @@ GIT_INLINE(void *) git_vector_get(git_vector *v, unsigned int position) #define git_vector_foreach(v, iter, elem) \ for ((iter) = 0; (iter) < (v)->length && ((elem) = (v)->contents[(iter)], 1); (iter)++ ) +#define git_vector_rforeach(v, iter, elem) \ + for ((iter) = (v)->length; (iter) > 0 && ((elem) = (v)->contents[(iter)-1], 1); (iter)-- ) + int git_vector_insert(git_vector *v, void *element); int git_vector_remove(git_vector *v, unsigned int idx); void git_vector_uniq(git_vector *v); diff --git a/tests-clay/attr/file.c b/tests-clay/attr/file.c new file mode 100644 index 000000000..0a5bff59d --- /dev/null +++ b/tests-clay/attr/file.c @@ -0,0 +1,236 @@ +#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(&file, cl_fixture("attr/attr0"))); + 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.negative); + cl_assert(!rule->match.directory); + cl_assert(!rule->match.fullpath); + + 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->name_len == 6); + 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(&file, cl_fixture("attr/attr1"))); + 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.negative); + cl_assert(!rule->match.directory); + cl_assert(!rule->match.fullpath); + 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->name_len == strlen("attr0")); + 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.negative); + + rule = get_rule(2); + cl_assert_strequal("pat2", rule->match.pattern); + cl_assert(rule->match.length == strlen("pat2")); + cl_assert(rule->match.directory); + cl_assert(!rule->match.fullpath); + + rule = get_rule(3); + cl_assert_strequal("pat3dir/pat3file", rule->match.pattern); + cl_assert(!rule->match.directory); + cl_assert(rule->match.fullpath); + + rule = get_rule(4); + cl_assert_strequal("pat4.*", rule->match.pattern); + cl_assert(!rule->match.negative); + cl_assert(!rule->match.directory); + cl_assert(!rule->match.fullpath); + + 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.negative); + cl_assert(!rule->match.directory); + cl_assert(!rule->match.fullpath); + + 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->name_len == strlen(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(&file, cl_fixture("attr/attr2"))); + 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(&file, cl_fixture("attr/attr3"))); + 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..870dcd343 --- /dev/null +++ b/tests-clay/attr/lookup.c @@ -0,0 +1,237 @@ +#include "clay_libgit2.h" +#include "attr.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(&file, cl_fixture("attr/attr0"))); + 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++) { + /* Put this in because I was surprised that all the tests passed */ + /* fprintf(stderr, "checking '%s' attr %s == %s\n", */ + /* c->path, c->attr, c->expected); */ + + 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(&file, cl_fixture("attr/attr1"))); + 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(&file, cl_fixture("attr/attr2"))); + 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(&file, cl_fixture("attr/attr3"))); + 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..b6815f3ba --- /dev/null +++ b/tests-clay/attr/repo.c @@ -0,0 +1,140 @@ +#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. + */ + cl_fixture_sandbox("attr"); + cl_git_pass(p_rename("attr/.gitted", "attr/.git")); + 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_test3", "repoattr", GIT_ATTR_TRUE }, + { "root_test3", "rootattr", 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 */ +} diff --git a/tests-clay/clay.h b/tests-clay/clay.h index 210273532..8237991c0 100644 --- a/tests-clay/clay.h +++ b/tests-clay/clay.h @@ -59,6 +59,19 @@ 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__match_variants(void); +extern void test_attr_lookup__simple(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_buf_basic__printf(void); extern void test_buf_basic__resize(void); extern void test_config_add__cleanup(void); diff --git a/tests-clay/clay_main.c b/tests-clay/clay_main.c index af9e08877..49a867698 100644 --- a/tests-clay/clay_main.c +++ b/tests-clay/clay_main.c @@ -108,6 +108,23 @@ 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}, + {"match_variants", &test_attr_lookup__match_variants}, + {"simple", &test_attr_lookup__simple} +}; +static const struct clay_func _clay_cb_attr_repo[] = { + {"foreach", &test_attr_repo__foreach}, + {"get_many", &test_attr_repo__get_many}, + {"get_one", &test_attr_repo__get_one} +}; static const struct clay_func _clay_cb_buf_basic[] = { {"printf", &test_buf_basic__printf}, {"resize", &test_buf_basic__resize} @@ -303,6 +320,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, 4 + }, + { + "attr::repo", + {"initialize", &test_attr_repo__initialize}, + {"cleanup", &test_attr_repo__cleanup}, + _clay_cb_attr_repo, 3 + }, + { "buf::basic", {NULL, NULL}, {NULL, NULL}, @@ -520,8 +555,8 @@ static const struct clay_suite _clay_suites[] = { } }; -static size_t _clay_suite_count = 36; -static size_t _clay_callback_count = 120; +static size_t _clay_suite_count = 39; +static size_t _clay_callback_count = 131; /* Core test functions */ static void diff --git a/tests/resources/attr/.gitattributes b/tests/resources/attr/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..f2c6d717cf4a5a3e6b02684155ab07b766982165 GIT binary patch literal 54 jcmdN=D9X<-Nh~QT;sP<^OHzwVj1+V+1&tLH;euQMCQB2k literal 0 HcmV?d00001 diff --git a/tests/resources/attr/.gitted/HEAD b/tests/resources/attr/.gitted/HEAD new file mode 100644 index 0000000000000000000000000000000000000000..cb089cd89a7d7686d284d8761201649346b5aa1c GIT binary patch literal 23 ecmXR)O|w!cN=+-)&qz&7Db~+TEG|hc;sO9;xClW2 literal 0 HcmV?d00001 diff --git a/tests/resources/attr/.gitted/config b/tests/resources/attr/.gitted/config new file mode 100644 index 0000000000000000000000000000000000000000..af107929f2da5ebccf17f97084f24e4ae204b18f GIT binary patch literal 111 zcmX}k!3}^Q429t{Ou+~);3URFg+fRQC2cWrd-3A&OaAwc$bzSLf`hdh%ad6e*o~r< pd)UL~U9N$t+PQ$;d2LNXyJgRZve!Elw`VEGWs$&r??@ Q$yWgB0LrH#Y0~2Y0PnOK(EtDd literal 0 HcmV?d00001 diff --git a/tests/resources/attr/.gitted/index b/tests/resources/attr/.gitted/index new file mode 100644 index 0000000000000000000000000000000000000000..6841fb2ec7480822307fedc7917037b8befc465b GIT binary patch literal 1072 zcmZ?q402{*U|<4bUcdKSwgPDe7|jO~*KpKjU}#*zz`*zwC?x{KW}l8-7eDV6WtYvA z;TXD_eS6vr#Z(49z4XkI#FCPt%%swi)MBtX?|jOUX|Q?fpRS^r#~GU6(^TE(#k4kN zb@`n|dp18(+pw8|6=aqHNIQccc5|+wn!~uwy5x4%=0<_o1DE=nrY^6~YML==62u%s zeCAw7HHS$FBIvr76rxTz=lq{cIVy zit_VI;(;Mu0`VN!oGm*c=0Rv^{OOZu9>kLn^L9ba1Jh9RfRTcd9tgP);#shHJ7OT_ zL1>71P8Mk9@%#=*-?;Ac8mU{}=5zOKzgQG{zqW#b5AI(Ry^@L&h&fxCk!h&=Y|zXz zIKJv`Id3HU3ilYzm+ur8Rd?)9k7W=oE=@|wEYinJwxIYZgqRDVA?7-4Kr>hTY4UmZ zh!5A?K5q0bW6WAuzw)W)5(Z(Yxey8^g8bg^$b3n*|h=_OuZ_r71Adpc=;{6@a1Nb&o)Z!ujH<%(vN^?j; z#-m_9AqF#NBGhQvNpd!#Fg)J9YprnCnzg}&TZ^iu@!gYU!pTW>RKzQ)>16n#SkpKVy literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..cfd1f9525d4d9b282b2d914c57041b4c10452698 GIT binary patch literal 170 zcma)!O9}!p5CzwIieBnM>3n*|h=_OuZ_r71Adpc=;{6@a1Nb&o)Z!ujH<%(vN^?j; z#-m_9AqF#NBGhQvNpd!#Fg)J9YprnCnzg}&TZ^iu@!gYU!pTW>RKzQ)>16n#SkpKVy literal 0 HcmV?d00001 diff --git a/tests/resources/attr/.gitted/objects/29/29de282ce999e95183aedac6451d3384559c4b b/tests/resources/attr/.gitted/objects/29/29de282ce999e95183aedac6451d3384559c4b new file mode 100644 index 0000000000000000000000000000000000000000..ad84f0854ede5ceffa46bdb83927f961626e560c GIT binary patch literal 58 zcmV-A0LA}!0V^p=O;s>4WH2-^Ff%bxC@xJ($t*I8FG(#fF=V)N>Xw_X?TCD;cNeeRIo+J}L z1Gebc0w#|gMO#+es)T<|o~W_e!0N31W|`0glDs$Ahmke$U%5}td#LG$e OFJMp&g~K-|%DK85WSqMI literal 0 HcmV?d00001 diff --git a/tests/resources/attr/.gitted/objects/2d/e7dfe3588f3c7e9ad59e7d50ba90e3329df9d9 b/tests/resources/attr/.gitted/objects/2d/e7dfe3588f3c7e9ad59e7d50ba90e3329df9d9 new file mode 100644 index 0000000000000000000000000000000000000000..e0fd0468e8db788cd831d69d4ac10a6a427ba884 GIT binary patch literal 124 zcmV-?0E7Q{0V^p=O;s>7GGj0_FfcPQQP4}zEJ-XWDauSLElDkAIKJv`Id3HU3ilYz zm+ur8Rd?)9kA e!|#Cfjq5(Ik-Ft=K6lUdi$$ULYbyYn_BZ6rjW<~U literal 0 HcmV?d00001 diff --git a/tests/resources/attr/.gitted/objects/3b/74db7ab381105dc0d28f8295a77f6a82989292 b/tests/resources/attr/.gitted/objects/3b/74db7ab381105dc0d28f8295a77f6a82989292 new file mode 100644 index 0000000000000000000000000000000000000000..e5cef35fa4e79e2ad52c112c675747678f8c77ef GIT binary patch literal 276 zcmV+v0qg#F0ZouiZo)7Sg}e4CrplrcQWGE$!is|ci%IMd3ulbtDL*}Z?MYhI3w!*@ zZ^rL6&Nr|r>$eOLT0abi7&`Bqe;5tT3xXdEG!E$s&XNf#E|3)!O&qd1bd;BWB~0=pbw4_eGw~#F8JbCHpMvaO3gUO3tB2t ac4VGBwT_|2XpQe=ZZx+SzW)Iljg8nEEP|l` literal 0 HcmV?d00001 diff --git a/tests/resources/attr/.gitted/objects/45/141a79a77842c59a63229403220a4e4be74e3d b/tests/resources/attr/.gitted/objects/45/141a79a77842c59a63229403220a4e4be74e3d new file mode 100644 index 0000000000000000000000000000000000000000..5b58ef0243fd3afc23f968ac6364b1da72a09f56 GIT binary patch literal 36 scmbbpXS{V*Jz-+dlx4XD0EjONFaQ7m literal 0 HcmV?d00001 diff --git a/tests/resources/attr/.gitted/objects/6b/ab5c79cd5140d0f800917f550eb2a3dc32b0da b/tests/resources/attr/.gitted/objects/6b/ab5c79cd5140d0f800917f550eb2a3dc32b0da new file mode 100644 index 0000000000000000000000000000000000000000..f51e11ccc8d7d26a490d9793fdb0c9880a6af217 GIT binary patch literal 135 zcmV;20C@j+0iBK83Bxc9Mf>e3v_OkwCw@REr6Y6$#ZjoiX~0qTub~_C>%!q2Zr-=8 zC{XklZ6c5n6UCS^<`8JGa|}!?k7ONtmm+&woReVq)c4-tbsmS*3Qwu4G?=)xS>*AX pTi&-jkh8-;KBW<^7!ggU{ZqUCPi@MlXyFPfhr+_j<_ieFJS|b)LaYD) literal 0 HcmV?d00001 diff --git a/tests/resources/attr/.gitted/objects/c0/091889c0c77142b87a1fa5123a6398a61d33e7 b/tests/resources/attr/.gitted/objects/c0/091889c0c77142b87a1fa5123a6398a61d33e7 new file mode 100644 index 0000000000000000000000000000000000000000..11dc63c79e90d929ded1c24084b5eadbe4c49e5a GIT binary patch literal 290 zcmV+-0p0$10V^p=O;s?qG-oh0FfcPQQP4}zEJ-XWDauSLElDkA_;l>L_<64=yKJTm z$I#X6+tX$!rb1PMR2eXY=JzyJ_jxg`jaglOXVIR`kJL77Mp9tNU|n*%YICDN?14-D zO;eZGXEn{3Gzm$85ra*~i*d%c$ZZQf9} zynEluN6#PRKou0_=a<9--C1JD;3^_jxxB*Z=&WR=Da=Y-e%{ahY|&L25v9tQC{-qU zB^4zMzXQ@YuKT=3>Xx_p+&$Yb7KPrgtuQeF0)^tzq?F7e2HofPA4l}t)Xlm&uQp)U ogvUm6f8In^V8o!Qc~3*<<;<6X&FgL*bCor237z8&08Q7Q26U*DKL7v# literal 0 HcmV?d00001 diff --git a/tests/resources/attr/.gitted/objects/c4/85abe35abd4aa6fd83b076a78bbea9e2e7e06c b/tests/resources/attr/.gitted/objects/c4/85abe35abd4aa6fd83b076a78bbea9e2e7e06c new file mode 100644 index 0000000000000000000000000000000000000000..58569ca0ee1024fc51434779cb9d6158923d5e6c GIT binary patch literal 129 zcmV-{0Dk{?0WHe03c@fHMq%eX#V>9`3Pslr1v|Eb_yV`-O%qL;ki?>IuT-~>!x>!| zQJ>Dc18#;hgA#*Z%}Y%OakzkjDVas_X_+~x3W<67 SB^jwjw)uIfTwDMmkQ0IQ4H-B9 literal 0 HcmV?d00001 diff --git a/tests/resources/attr/.gitted/objects/dc/cada462d3df8ac6de596fb8c896aba9344f941 b/tests/resources/attr/.gitted/objects/dc/cada462d3df8ac6de596fb8c896aba9344f941 new file mode 100644 index 0000000000000000000000000000000000000000..ef62f8b9ddbc1a55a405edbab31f1d5fdf29fcfd GIT binary patch literal 35 rcmbHu6c1iRi0DmMA A9{>OV literal 0 HcmV?d00001 diff --git a/tests/resources/attr/.gitted/objects/fb/5067b1aef3ac1ada4b379dbcb7d17255df7d78 b/tests/resources/attr/.gitted/objects/fb/5067b1aef3ac1ada4b379dbcb7d17255df7d78 new file mode 100644 index 0000000000000000000000000000000000000000..6c8ff837e231ec5208dcfac65fe557371fbfb44e GIT binary patch literal 28 kcmb4V4FlPAMHeEiRxdCbJ%xQ5LU0JnAwK>z>% literal 0 HcmV?d00001 diff --git a/tests/resources/attr/.gitted/refs/heads/master b/tests/resources/attr/.gitted/refs/heads/master new file mode 100644 index 0000000000000000000000000000000000000000..279272e5c7d1cb0f5d79f2b9e93bda33b5469622 GIT binary patch literal 41 ucmV~$NdW*L2n4{tX%-NXI2_VHf-@&0walxPHX3sQCmU6~_ldN)(Bn!rpW1=z@SZqE$eI?p0s)a;7Xh_WzfY5}# zcP!8b@594cxV#{S-oeEqe}X4=(WC{s3P29hx#3SI9STqcVw>7HYfJ%m90-L1M+gip z_b5$g%nfA@C^F2=TdEgYFEUOfT!{Con4$-6-4S2Xz+J zmEu})ts}Qb{BY;P;rM)gpOtT+fCowU&wcu4a;0unv^(jG6T}-fE~;#&ArRlulT9tM zld%s6z!>SuImrk%xXAC4BKl%627L_GuWXEQ+KriTiB}}9a(3*VGPX{krf6Mgb#5}Z HSHAxN{>6)W literal 0 HcmV?d00001 diff --git a/tests/resources/attr/attr2 b/tests/resources/attr/attr2 new file mode 100644 index 0000000000000000000000000000000000000000..2c66e14f77196ea763fb1e41612c1aa2bc2d8ed2 GIT binary patch literal 552 zcmZ`$yKciU4BY({OtJ+K8Yg`YTDo;F(3y!CTc}JKBAwKKU&%=kw?G95;sd-po~jj` zFnLrTSs*f?h;O(fR;j8UbpztNK2V8kFicIg297$gqhl#e@6PZJcN$k}BTP?T`KwYn+E%D<-e9NDp8-n^Q3ygSf@;l*(tpS5XizX3< zFq#m#xoEgsLyLVs+L2^Ytw37>rZ0uynQ}`&6Z)JqAUz&P3h^^wgPtv5-m#@*>+Nn^ z!#}4??9pvtb)IzQG|5teOADefA;1o_;v4hW~=dYEePjI0QFCnd`W4Pui*s w|N8y`Z+Xn~XIV96_nW)b7LpH(at!)lzlV*$XX}_bot^I-Dbo$qO*IxS$ QMm9=mUQ3QN-g=H|2PcXyMF0Q* literal 0 HcmV?d00001 diff --git a/tests/resources/attr/root_test1 b/tests/resources/attr/root_test1 new file mode 100644 index 0000000000000000000000000000000000000000..45141a79a77842c59a63229403220a4e4be74e3d GIT binary patch literal 20 bcmeZB&B@7ENGr Date: Wed, 28 Dec 2011 23:28:50 -0800 Subject: [PATCH 2/4] Add support for macros and cache flush API. Add support for git attribute macro definitions. Also, add support for cache flush API to clear the attribute file content cache when needed. Additionally, improved the handling of global and system files, making common utility functions in fileops and converting config and attr to both use the common functions. Adds a bunch more tests and fixed some memory leaks. Note that adding macros required me to use refcounted attribute assignment definitions, which complicated, but probably improved memory usage. --- include/git2/attr.h | 24 +++ src/attr.c | 131 +++++++++++-- src/attr.h | 21 --- src/attr_file.c | 175 ++++++++++++------ src/attr_file.h | 43 ++++- src/config.c | 79 +------- src/config.h | 1 + src/fileops.c | 131 +++++++++++++ src/fileops.h | 24 +++ src/repository.c | 2 +- src/repository.h | 2 +- src/util.h | 1 - src/win32/utf-conv.c | 5 + src/win32/utf-conv.h | 1 + tests-clay/attr/file.c | 35 ++-- tests-clay/attr/lookup.c | 38 +++- tests-clay/attr/repo.c | 48 ++++- tests-clay/clay.h | 3 + tests-clay/clay_main.c | 11 +- tests/resources/attr/.gitattributes | Bin 54 -> 0 bytes tests/resources/attr/.gitted/index | Bin 1072 -> 1304 bytes tests/resources/attr/.gitted/info/attributes | Bin 12 -> 28 bytes tests/resources/attr/.gitted/logs/HEAD | Bin 170 -> 332 bytes .../attr/.gitted/logs/refs/heads/master | Bin 170 -> 332 bytes .../3e/42ffc54a663f9401cc25843d6c0e71a33e4249 | Bin 0 -> 596 bytes .../60/5812ab7fe421fdd325a935d35cb06a9234a7d7 | Bin 0 -> 162 bytes .../94/da4faa0a6bfb8ee6ccf7153801a69202b31857 | Bin 0 -> 124 bytes .../99/eae476896f4907224978b88e5ecaa6c5bb67a9 | Bin 0 -> 95 bytes .../9f/b40b6675dde60b5697afceae91b66d908c02d9 | Bin 0 -> 151 bytes .../a5/6bbcecaeac760cc26239384d2d4c614e7e4320 | Bin 0 -> 351 bytes .../d8/00886d9c86731ae5c4a62b0b77c437015e00d2 | Bin 0 -> 18 bytes .../ff/69f8639ce2e6010b3f33a74160aad98b48da2b | Bin 0 -> 18 bytes .../resources/attr/.gitted/refs/heads/master | Bin 41 -> 41 bytes tests/resources/attr/binfile | Bin 0 -> 3 bytes tests/resources/attr/gitattributes | Bin 0 -> 162 bytes tests/resources/attr/macro_test | Bin 0 -> 3 bytes tests/resources/attr/subdir/.gitattributes | Bin 49 -> 98 bytes tests/resources/attr/subdir/abc | Bin 0 -> 1245 bytes 38 files changed, 559 insertions(+), 216 deletions(-) delete mode 100644 src/attr.h delete mode 100644 tests/resources/attr/.gitattributes create mode 100644 tests/resources/attr/.gitted/objects/3e/42ffc54a663f9401cc25843d6c0e71a33e4249 create mode 100644 tests/resources/attr/.gitted/objects/60/5812ab7fe421fdd325a935d35cb06a9234a7d7 create mode 100644 tests/resources/attr/.gitted/objects/94/da4faa0a6bfb8ee6ccf7153801a69202b31857 create mode 100644 tests/resources/attr/.gitted/objects/99/eae476896f4907224978b88e5ecaa6c5bb67a9 create mode 100644 tests/resources/attr/.gitted/objects/9f/b40b6675dde60b5697afceae91b66d908c02d9 create mode 100644 tests/resources/attr/.gitted/objects/a5/6bbcecaeac760cc26239384d2d4c614e7e4320 create mode 100644 tests/resources/attr/.gitted/objects/d8/00886d9c86731ae5c4a62b0b77c437015e00d2 create mode 100644 tests/resources/attr/.gitted/objects/ff/69f8639ce2e6010b3f33a74160aad98b48da2b create mode 100644 tests/resources/attr/binfile create mode 100644 tests/resources/attr/gitattributes create mode 100644 tests/resources/attr/macro_test create mode 100644 tests/resources/attr/subdir/abc diff --git a/include/git2/attr.h b/include/git2/attr.h index d585937b7..f4c5975a6 100644 --- a/include/git2/attr.h +++ b/include/git2/attr.h @@ -50,6 +50,30 @@ GIT_EXTERN(int) git_attr_foreach( 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 index d8e7095b1..fac2fa4b9 100644 --- a/src/attr.c +++ b/src/attr.c @@ -1,19 +1,17 @@ -#include "attr.h" -#include "buffer.h" +#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 "/etc/gitattributes" -#if GIT_WIN32 -#define GIT_ATTR_FILE_WIN32 L"%PROGRAMFILES%\\Git\\etc\\gitattributes" -#endif +#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, @@ -180,6 +178,44 @@ cleanup: } +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); + git__free(macro); + } + + return error; +} + + /* add git_attr_file to vector of files, loading if needed */ static int push_attrs( git_repository *repo, @@ -193,13 +229,6 @@ static int push_attrs( git_attr_file *file; int add_to_cache = 0; - if (cache->files == NULL) { - cache->files = git_hashtable_alloc( - 8, git_hash__strhash_cb, git_hash__strcmp_cb); - if (!cache->files) - return git__throw(GIT_ENOMEM, "Could not create attribute cache"); - } - if ((error = git_path_prettify(&path, filename, base)) < GIT_SUCCESS) { if (error == GIT_EOSERR) /* file was not found -- ignore error */ @@ -210,7 +239,7 @@ static int push_attrs( /* either get attr_file from cache or read from disk */ file = git_hashtable_lookup(cache->files, path.ptr); if (file == NULL) { - error = git_attr_file__from_file(&file, path.ptr); + error = git_attr_file__from_file(repo, path.ptr, &file); add_to_cache = (error == GIT_SUCCESS); } @@ -238,6 +267,9 @@ static int collect_attr_files( 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; @@ -288,8 +320,13 @@ static int collect_attr_files( git_config_free(cfg); } - if (error == GIT_SUCCESS) - error = push_attrs(repo, files, NULL, GIT_ATTR_FILE_SYSTEM); + 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) { @@ -302,10 +339,64 @@ static int collect_attr_files( } -void git_repository__attr_cache_free(git_attr_cache *attrs) +static int attr_cache_init(git_repository *repo) { - if (attrs && attrs->files) { - git_hashtable_free(attrs->files); - attrs->files = NULL; + 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.h b/src/attr.h deleted file mode 100644 index 518fb9d3b..000000000 --- a/src/attr.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2009-2011 the libgit2 contributors - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_attr_h__ -#define INCLUDE_attr_h__ - -#include "hashtable.h" -#include "attr_file.h" - -/* EXPORT */ -typedef struct { - git_hashtable *files; /* hash path to git_attr_file */ -} git_attr_cache; - -extern void git_repository__attr_cache_free(git_attr_cache *attrs); - -#endif - diff --git a/src/attr_file.c b/src/attr_file.c index 5d159db00..0b1eb1f67 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -1,17 +1,33 @@ #include "common.h" -#include "attr_file.h" +#include "repository.h" #include "filebuf.h" #include const char *git_attr__true = "[internal]__TRUE__"; const char *git_attr__false = "[internal]__FALSE__"; -static int parse_fnmatch(git_attr_fnmatch *spec, const char **base); -static int parse_assigns(git_vector *assigns, const char **base); -static int free_rule(git_attr_rule *rule); +static int 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); -int git_attr_file__from_buffer(git_attr_file **out, const char *buffer) +int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro) +{ + unsigned int i; + git_attr_assignment *assign; + + if (macro->assigns.length == 0) + return git__throw(GIT_EMISSINGOBJDATA, "git attribute macro with no values"); + + git_vector_foreach(¯o->assigns, i, assign) { + GIT_REFCOUNT_OWN(assign, macro); + GIT_REFCOUNT_INC(assign); + } + + 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; @@ -42,13 +58,21 @@ int git_attr_file__from_buffer(git_attr_file **out, const char *buffer) } /* parse the next "pattern attr attr attr" line */ - if (!(error = parse_fnmatch(&rule->match, &scan)) && - !(error = parse_assigns(&rule->assigns, &scan))) - error = git_vector_insert(&attrs->rules, rule); + if (!(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) { - free_rule(rule); /* release anything partially allocated */ + git_attr_rule__free(rule); /* free anything partially allocated */ if (error == GIT_ENOTFOUND) error = GIT_SUCCESS; } else { @@ -58,6 +82,7 @@ int git_attr_file__from_buffer(git_attr_file **out, const char *buffer) cleanup: if (error != GIT_SUCCESS) { + git__free(rule); git_attr_file__free(attrs); git__free(attrs); } else { @@ -67,7 +92,8 @@ cleanup: return error; } -int git_attr_file__from_file(git_attr_file **out, const char *path) +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; @@ -75,7 +101,7 @@ int git_attr_file__from_file(git_attr_file **out, const char *path) *out = NULL; if ((error = git_futils_readbuffer(&fbuf, path)) < GIT_SUCCESS || - (error = git_attr_file__from_buffer(out, fbuf.data)) < 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 { @@ -97,7 +123,7 @@ void git_attr_file__free(git_attr_file *file) return; git_vector_foreach(&file->rules, i, rule) { - free_rule(rule); + git_attr_rule__free(rule); } git_vector_free(&file->rules); @@ -153,15 +179,15 @@ int git_attr_rule__match_path( { int matched = FNM_NOMATCH; - if (rule->match.directory && !path->is_dir) + if (rule->match.flags & GIT_ATTR_FNMATCH_DIRECTORY && !path->is_dir) return matched; - if (rule->match.fullpath) + 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.negative) + if (rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE) matched = (matched == GIT_SUCCESS) ? FNM_NOMATCH : GIT_SUCCESS; return matched; @@ -232,7 +258,7 @@ int git_attr_path__init( * GIT_ENOTFOUND if the fnmatch does not require matching, or * another error code there was an actual problem. */ -static int parse_fnmatch( +static int git_attr_fnmatch__parse( git_attr_fnmatch *spec, const char **base) { @@ -251,21 +277,31 @@ static int parse_fnmatch( goto skip_to_eol; } - if (*pattern == '!') { - spec->negative = 1; - pattern++; - } else { - spec->negative = 0; + 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++; } - spec->fullpath = 0; slash_count = 0; for (scan = pattern; *scan != '\0'; ++scan) { if (isspace(*scan) && *(scan - 1) != '\\') break; if (*scan == '/') { - spec->fullpath = 1; + spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH; slash_count++; } } @@ -292,11 +328,9 @@ static int parse_fnmatch( if (pattern[spec->length - 1] == '/') { spec->length--; spec->pattern[spec->length] = '\0'; - spec->directory = 1; + spec->flags = spec->flags | GIT_ATTR_FNMATCH_DIRECTORY; if (--slash_count <= 0) - spec->fullpath = 0; - } else { - spec->directory = 0; + spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH; } return GIT_SUCCESS; @@ -323,7 +357,21 @@ static int sort_by_hash_and_name(const void *a_raw, const void *b_raw) return strcmp(b->name, a->name); } -static int parse_assigns( +static void free_assign(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); +} + +int git_attr_assignment__parse( + git_repository *repo, git_vector *assigns, const char **base) { @@ -333,7 +381,7 @@ static int parse_assigns( assert(assigns && !assigns->length); - while (*scan && *scan != '\n') { + while (*scan && *scan != '\n' && error == GIT_SUCCESS) { const char *name_start, *value_start; /* skip leading blanks */ @@ -369,8 +417,7 @@ static int parse_assigns( ((assign->name_hash << 5) + assign->name_hash) + *scan; scan++; } - assign->name_len = scan - name_start; - if (assign->name_len <= 0) { + if (scan == name_start) { /* must have found lone prefix (" - ") or leading = ("=foo") * or end of buffer -- advance until whitespace and continue */ @@ -378,6 +425,13 @@ static int parse_assigns( 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); @@ -394,11 +448,34 @@ static int parse_assigns( } } - /* allocate permanent storage for name */ - assign->name = git__strndup(name_start, assign->name_len); - if (!assign->name) { - error = GIT_ENOMEM; - break; + /* expand macros (if given a repo) */ + if (repo != NULL) { + git_attr_rule *macro = + git_hashtable_lookup(repo->attrcache.macros, assign->name); + + if (macro != NULL) { + unsigned int i; + git_attr_assignment *massign; + + /* issue warning: if assign->value != GIT_ATTR_TRUE */ + + git__free(assign->name); + assign->name = NULL; + if (assign->is_allocated) { + git__free((void *)assign->value); + assign->value = NULL; + } + + git_vector_foreach(¯o->assigns, i, massign) { + error = git_vector_insert(assigns, massign); + if (error != GIT_SUCCESS) + break; + GIT_REFCOUNT_INC(&massign->rc); + } + + /* continue to next assignment */ + continue; + } } /* insert allocated assign into vector */ @@ -417,40 +494,34 @@ static int parse_assigns( git_vector_sort(assigns); } - if (assign != NULL) { - git__free(assign->name); - if (assign->is_allocated) - git__free((void *)assign->value); - git__free(assign); - } + if (assign != NULL) + free_assign(assign); while (*scan && *scan != '\n') scan++; + if (*scan == '\n') scan++; + *base = scan; return error; } -static int free_rule(git_attr_rule *rule) +void git_attr_rule__free(git_attr_rule *rule) { unsigned int i; git_attr_assignment *assign; if (!rule) - return GIT_SUCCESS; + return; git__free(rule->match.pattern); rule->match.pattern = NULL; rule->match.length = 0; git_vector_foreach(&rule->assigns, i, assign) { - git__free(assign->name); - assign->name = NULL; - - if (assign->is_allocated) { - git__free((void *)assign->value); - assign->value = NULL; - } + if (GIT_REFCOUNT_OWNER(assign) == rule) + GIT_REFCOUNT_OWN(assign, NULL); + GIT_REFCOUNT_DEC(assign, free_assign); } - return GIT_SUCCESS; + git_vector_free(&rule->assigns); } diff --git a/src/attr_file.h b/src/attr_file.h index 4774f148c..bed440d61 100644 --- a/src/attr_file.h +++ b/src/attr_file.h @@ -9,36 +9,41 @@ #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; - int negative; - int directory; - int fullpath; + unsigned int flags; } git_attr_fnmatch; typedef struct { + git_refcount unused; const char *name; - unsigned long name_hash; + unsigned long name_hash; } git_attr_name; typedef struct { + git_refcount rc; /* for macros */ char *name; unsigned long name_hash; - size_t name_len; const char *value; int is_allocated; } git_attr_assignment; typedef struct { git_attr_fnmatch match; - git_vector assigns; /* */ + git_vector assigns; /* vector of */ } git_attr_rule; typedef struct { - char *path; - git_vector rules; /* */ + char *path; /* cache the path this was loaded from */ + git_vector rules; /* vector of */ } git_attr_file; typedef struct { @@ -47,12 +52,20 @@ typedef struct { 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_attr_file **out, const char *buf); -extern int git_attr_file__from_file(git_attr_file **out, const char *path); +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); @@ -74,6 +87,8 @@ 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); @@ -84,4 +99,12 @@ extern git_attr_assignment *git_attr_rule__lookup_assignment( 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 ed7c947ed..29f1ee27c 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/repository.c b/src/repository.c index e0d4c6387..a94ecce55 100644 --- a/src/repository.c +++ b/src/repository.c @@ -59,7 +59,7 @@ void git_repository_free(git_repository *repo) git_cache_free(&repo->objects); git_repository__refcache_free(&repo->references); - git_repository__attr_cache_free(&repo->attrcache); + git_attr_cache_flush(repo); git__free(repo->path_repository); git__free(repo->workdir); diff --git a/src/repository.h b/src/repository.h index 5274fc1d0..82052158a 100644 --- a/src/repository.h +++ b/src/repository.h @@ -19,7 +19,7 @@ #include "refs.h" #include "buffer.h" #include "odb.h" -#include "attr.h" +#include "attr_file.h" #define DOT_GIT ".git" #define GIT_DIR DOT_GIT "/" diff --git a/src/util.h b/src/util.h index be978a6a5..4b1104b7b 100644 --- a/src/util.h +++ b/src/util.h @@ -109,7 +109,6 @@ extern void **git__bsearch(const void *key, void **base, size_t nmemb, int (*compar)(const void *, const void *)); extern int git__strcmp_cb(const void *a, const void *b); -extern uint32_t git__strhash_cb(const void *key, int hash_id); typedef struct { short refcount; diff --git a/src/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 index 0a5bff59d..d9e2d5701 100644 --- a/tests-clay/attr/file.c +++ b/tests-clay/attr/file.c @@ -8,7 +8,7 @@ void test_attr_file__simple_read(void) { git_attr_file *file = NULL; - cl_git_pass(git_attr_file__from_file(&file, cl_fixture("attr/attr0"))); + 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); @@ -16,15 +16,12 @@ void test_attr_file__simple_read(void) cl_assert(rule != NULL); cl_assert_strequal("*", rule->match.pattern); cl_assert(rule->match.length == 1); - cl_assert(!rule->match.negative); - cl_assert(!rule->match.directory); - cl_assert(!rule->match.fullpath); + 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->name_len == 6); cl_assert(assign->value == GIT_ATTR_TRUE); cl_assert(!assign->is_allocated); @@ -37,7 +34,7 @@ void test_attr_file__match_variants(void) git_attr_rule *rule; git_attr_assignment *assign; - cl_git_pass(git_attr_file__from_file(&file, cl_fixture("attr/attr1"))); + 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); @@ -48,38 +45,31 @@ void test_attr_file__match_variants(void) cl_assert(rule); cl_assert_strequal("pat0", rule->match.pattern); cl_assert(rule->match.length == strlen("pat0")); - cl_assert(!rule->match.negative); - cl_assert(!rule->match.directory); - cl_assert(!rule->match.fullpath); + 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->name_len == strlen("attr0")); 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.negative); + 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.directory); - cl_assert(!rule->match.fullpath); + 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.directory); - cl_assert(rule->match.fullpath); + cl_assert(rule->match.flags == GIT_ATTR_FNMATCH_FULLPATH); rule = get_rule(4); cl_assert_strequal("pat4.*", rule->match.pattern); - cl_assert(!rule->match.negative); - cl_assert(!rule->match.directory); - cl_assert(!rule->match.fullpath); + cl_assert(rule->match.flags == 0); rule = get_rule(5); cl_assert_strequal("*.pat5", rule->match.pattern); @@ -94,9 +84,7 @@ void test_attr_file__match_variants(void) 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.negative); - cl_assert(!rule->match.directory); - cl_assert(!rule->match.fullpath); + cl_assert(rule->match.flags == 0); rule = get_rule(9); cl_assert_strequal("pat9", rule->match.pattern); @@ -120,7 +108,6 @@ static void check_one_assign( 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->name_len == strlen(name)); cl_assert(assign->is_allocated == is_allocated); if (is_allocated) cl_assert_strequal(value, assign->value); @@ -134,7 +121,7 @@ void test_attr_file__assign_variants(void) git_attr_rule *rule; git_attr_assignment *assign; - cl_git_pass(git_attr_file__from_file(&file, cl_fixture("attr/attr2"))); + 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); @@ -199,7 +186,7 @@ void test_attr_file__check_attr_examples(void) git_attr_rule *rule; git_attr_assignment *assign; - cl_git_pass(git_attr_file__from_file(&file, cl_fixture("attr/attr3"))); + 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); diff --git a/tests-clay/attr/lookup.c b/tests-clay/attr/lookup.c index 870dcd343..fcade5225 100644 --- a/tests-clay/attr/lookup.c +++ b/tests-clay/attr/lookup.c @@ -1,5 +1,5 @@ #include "clay_libgit2.h" -#include "attr.h" +#include "attr_file.h" void test_attr_lookup__simple(void) { @@ -7,7 +7,7 @@ void test_attr_lookup__simple(void) git_attr_path path; const char *value = NULL; - cl_git_pass(git_attr_file__from_file(&file, cl_fixture("attr/attr0"))); + 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); @@ -41,10 +41,6 @@ static void run_test_cases(git_attr_file *file, test_case *cases) int error; for (c = cases; c->path != NULL; c++) { - /* Put this in because I was surprised that all the tests passed */ - /* fprintf(stderr, "checking '%s' attr %s == %s\n", */ - /* c->path, c->attr, c->expected); */ - cl_git_pass(git_attr_path__init(&path, c->path)); if (c->force_dir) @@ -136,7 +132,7 @@ void test_attr_lookup__match_variants(void) { NULL, NULL, NULL, 0, 0 } }; - cl_git_pass(git_attr_file__from_file(&file, cl_fixture("attr/attr1"))); + 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); @@ -194,7 +190,7 @@ void test_attr_lookup__assign_variants(void) { NULL, NULL, NULL, 0, 0 } }; - cl_git_pass(git_attr_file__from_file(&file, cl_fixture("attr/attr2"))); + 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); @@ -228,7 +224,31 @@ void test_attr_lookup__check_attr_examples(void) { NULL, NULL, NULL, 0, 0 } }; - cl_git_pass(git_attr_file__from_file(&file, cl_fixture("attr/attr3"))); + 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); diff --git a/tests-clay/attr/repo.c b/tests-clay/attr/repo.c index b6815f3ba..e80e24dbf 100644 --- a/tests-clay/attr/repo.c +++ b/tests-clay/attr/repo.c @@ -6,11 +6,14 @@ static git_repository *g_repo = NULL; void test_attr_repo__initialize(void) { - /* before each test, instantiate the attr repo from the fixtures and + /* 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")); } @@ -138,3 +141,46 @@ void test_attr_repo__foreach(void) &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 *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] == NULL); + 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] == NULL); + 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]); +} diff --git a/tests-clay/clay.h b/tests-clay/clay.h index 8237991c0..4a57926bf 100644 --- a/tests-clay/clay.h +++ b/tests-clay/clay.h @@ -65,6 +65,7 @@ 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__cleanup(void); @@ -72,6 +73,8 @@ 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); diff --git a/tests-clay/clay_main.c b/tests-clay/clay_main.c index 49a867698..ce881e45d 100644 --- a/tests-clay/clay_main.c +++ b/tests-clay/clay_main.c @@ -117,13 +117,16 @@ static const struct clay_func _clay_cb_attr_file[] = { 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[] = { {"foreach", &test_attr_repo__foreach}, {"get_many", &test_attr_repo__get_many}, - {"get_one", &test_attr_repo__get_one} + {"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}, @@ -329,13 +332,13 @@ static const struct clay_suite _clay_suites[] = { "attr::lookup", {NULL, NULL}, {NULL, NULL}, - _clay_cb_attr_lookup, 4 + _clay_cb_attr_lookup, 5 }, { "attr::repo", {"initialize", &test_attr_repo__initialize}, {"cleanup", &test_attr_repo__cleanup}, - _clay_cb_attr_repo, 3 + _clay_cb_attr_repo, 5 }, { "buf::basic", @@ -556,7 +559,7 @@ static const struct clay_suite _clay_suites[] = { }; static size_t _clay_suite_count = 39; -static size_t _clay_callback_count = 131; +static size_t _clay_callback_count = 134; /* Core test functions */ static void diff --git a/tests/resources/attr/.gitattributes b/tests/resources/attr/.gitattributes deleted file mode 100644 index f2c6d717cf4a5a3e6b02684155ab07b766982165..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54 jcmdN=D9X<-Nh~QT;sP<^OHzwVj1+V+1&tLH;euQMCQB2k diff --git a/tests/resources/attr/.gitted/index b/tests/resources/attr/.gitted/index index 6841fb2ec7480822307fedc7917037b8befc465b..9c5907386ff2aaed7325a3059c4256e1fee493d8 100644 GIT binary patch literal 1304 zcmZ?q402{*U|<4b0lz=Owm_NzM)QHhHPsRs7#f!_Ffe`vN{ImRqA9ohS8-+k?t6CT zyQl@@vPn#vCBhl_^wKj+5=%;oGLuS6Qj5XneAGCBOhe351)76m9%pENPg8ZD7t`99 z)#Y~yq15n;QjU4_xYRn!3C`t7*ohNf2`k z@tN}l)f^@bowSGk<&t^JlYh%OChABn+N0Zd31W^BK65^!n#0g`q;>Vkc)4T3s+-*&x3n3!aubt_ z^5cO~Rsu2u1pGc~S|ZcXbkKulo`|c6RORvtr=zoym8LK&art>a_p@c-D$36<0UHMi zblm3kl4u?z@Nt{hN1}OXx_p+&$Yb7KPrgtzh7T``1LT zqyijCH1pMyvd(-@b|ue>9er`9mL1)lzLG(-xHKsxvq&E^ zJAusG2sIZ>gUz+r?1E}8%UwIC|3|&j?58lEQEjo!;VWEh=j6%21vNJ@DH&u22;hk4 zNvP&Ah(Ap}?;i2tn%l>X-ertg3+q=t^<2Ur3^flzp+q;iZYW*OHzx$=DhPMN2bB%seihPWZuMjHF4~! zu1(xyh(qf7WEMtTayKUX0Oj6qZU$Qnr6C@1*fv>*$x{6NmYone2o3e5{^T04+%Bjb zn1;$3Og;mW+YtkigU}E;CyU84%$C;gw=zTIAT(5;4U$Je0cUW0)!lO5NcI)(F`6&m zDK4t+*q=R6(7lBa{SX?W-(kaK5f(?Fy?Ic3!8An9$$oMUNN)2~s6H?a)wg%@ c5f(?`v|Gma_Aj(MZ^3tE?c3^glR~{r0UQZ~`2YX_ diff --git a/tests/resources/attr/.gitted/info/attributes b/tests/resources/attr/.gitted/info/attributes index 93efc0c348f85379579cb921765eac9a37cf28ba..2e9643a53d5783c1404ab877bbed0a9d59aa4662 100644 GIT binary patch literal 28 jcmdN=C`v8JPb?`Z;!4!wOv}$#P)tfJQqWCGtl|OyfuRVf literal 12 TcmdN=C`v8JPb?`Z;^G1T8N36~ diff --git a/tests/resources/attr/.gitted/logs/HEAD b/tests/resources/attr/.gitted/logs/HEAD index cfd1f9525d4d9b282b2d914c57041b4c10452698..3c4045173ee5c21c20297c4eb612a22790c5382e 100644 GIT binary patch delta 96 zcmZ3*c!p`hs)?GK8fFHj7KTQNN#<#(CPs#7DJjNAriqrurYXjz$w>xgiIzsjCW+=L u<`VxgiIzsjCW+=L u<`V}X#l3Qp39;nZ)OMik*Y}N+ z5@5G#4xpIvoA=(lp){_f`-hKD?>^9mL!?FM->JCMC8t3SJsXQ%9 z^@4M`S6uK|pt!6~#Zo(!#?>3e19ZoJ%by{x@jO8+r6GFxY<5?SsNO}ihANskLfD=y zMMpaoTZ${;pgdFMx*hDPNd=HM#vK9_-xu`dnC#M4+b!xZdC~mP>Z&Cj2m+;5P#sgP zJy+5BqoB#DG!y$!^sC2kw6lv`Oop|#mKqgPVWT}pXS8MNRID(Of|BA%1DXj`;8@T) zk}|w1GzN%1)B*kVF5ggja2 zM4MrRUWT1~NFaj&^!yh~5-GDywmcT}OW%35~|9HgyJ95f>8YBP+#nw)?MB*-lUKLEX>J=}AdmWdNI#WD# eN}gsxo#jqj`XRSVh0m9=`a|s8w($*^PCE44>pKGg literal 0 HcmV?d00001 diff --git a/tests/resources/attr/.gitted/objects/99/eae476896f4907224978b88e5ecaa6c5bb67a9 b/tests/resources/attr/.gitted/objects/99/eae476896f4907224978b88e5ecaa6c5bb67a9 new file mode 100644 index 0000000000000000000000000000000000000000..8f5acc70a09092c8cd80ce5ab9f7a342d023920b GIT binary patch literal 95 zcmV-l0HFVP0UgLO4uBvG067HD@q1FfcPQQP4}zEJ-XWDauSLElDkAnEC2SS!cc{yOL+c zj=s24%Z~0&UkO#2n3T+5=k))mSDO74#xtrdwmE!-i|w2|q0+^rNhz5{@jydL3>ls# zpLdV=aLw)GM(;AltcCR}pL#CARA;1DQc=S2J0N}Iy3cE*Zh4!}-Lw5-QRw~J3IJAC FLlg=pNhkmS literal 0 HcmV?d00001 diff --git a/tests/resources/attr/.gitted/objects/a5/6bbcecaeac760cc26239384d2d4c614e7e4320 b/tests/resources/attr/.gitted/objects/a5/6bbcecaeac760cc26239384d2d4c614e7e4320 new file mode 100644 index 0000000000000000000000000000000000000000..d898ae9b8fe86f593b819c83e78110e18cde29dc GIT binary patch literal 351 zcmV-l0igbP0V^p=O;s>4H()R{FfcPQQP4}zEJ-XWDauSLElDkAm~zX16<7A}zGr8? zi&`))o5ZwPA{?p`q{@IHG{2{*y3dPgZOrQOJB#*gex$ZxGm-*B2J4dBRht_HVh>#E zZ<@NiKC5ZQq)A8$j2Lv%9{QI{<}FYDE$5i1BeiIcZr>#&1;z|VT30`g+UvFKZ}Wz- z<=y*MK6?Hj2dW?`GcPSOCzas_Lr3nMwqmKLN0w=Gmme`_jAMYAk(-!YlphcDO9{jO z%pb{f9zA2^wl`kxn6T<*x5q7Qs6j>f`6XaQh77JEQkBaqoQ}>)R+_@B#O3Gx+|L$W zl@U>@jEPcZqE}K;!tgsFedD^%You;@o6p^|{bEt*{n`o>10YZ+E=@|wEMl0yg*&bE x?lbPN>Fdv}o474^LJ!kTWCcbHnws}CbY9MU8Q8q;)-hLEz{H4+L0~6SCjdFf1%Chl literal 0 HcmV?d00001 diff --git a/tests/resources/attr/.gitted/objects/ff/69f8639ce2e6010b3f33a74160aad98b48da2b b/tests/resources/attr/.gitted/objects/ff/69f8639ce2e6010b3f33a74160aad98b48da2b new file mode 100644 index 0000000000000000000000000000000000000000..b736c0b2b31edac3a69995d507dd3c41d837b027 GIT binary patch literal 18 ZcmbqVrV$<)hh+Z|x--JroaOVB_jC&*L(qa-WEm`)-o8HM_6o!R literal 41 ucmV~$NdW*L2n4{tX%-NXI2_VHf-@&0walxPHX3sQCmU6~p^?HJ)u$W}clP;b@=Ugv>K)P%7ugrZ9%Rrq9Mwz0;nNcSG(2@Sg<5J-K LQWE>JKAXmG4fQyj literal 0 HcmV?d00001 diff --git a/tests/resources/attr/macro_test b/tests/resources/attr/macro_test new file mode 100644 index 0000000000000000000000000000000000000000..ff69f8639ce2e6010b3f33a74160aad98b48da2b GIT binary patch literal 3 Kcma#d=K=r$_5jlW literal 0 HcmV?d00001 diff --git a/tests/resources/attr/subdir/.gitattributes b/tests/resources/attr/subdir/.gitattributes index 210f3a8ba2dffaed2ba0e2ac1fc6bfb4f16f544b..99eae476896f4907224978b88e5ecaa6c5bb67a9 100644 GIT binary patch delta 55 zcmXp^nqXj*n54zY$*GW=T9lq@o0gfAR+P$>n3N0>(@o3ISI|vLEaKAA0}G@T<(E`( F0RXQ14-fzV delta 6 NcmYc?oM6Dn1po#k0bc+B diff --git a/tests/resources/attr/subdir/abc b/tests/resources/attr/subdir/abc new file mode 100644 index 0000000000000000000000000000000000000000..3e42ffc54a663f9401cc25843d6c0e71a33e4249 GIT binary patch literal 1245 zcmah}!LHLV5Ii?uu{=1mPiZPZT#z_Cgh0Kb-jSPZ8cU8{Y^Npf>zTDf)AoVLp;g@V z?9A+JU(=35q*fcI*1Mh#I;t4G-Vc$(^23Vwm+Fs(KNiKdrO^%4sS{J|ILN1GzP*y5 zP)tNiZRyRo?e52y?e~qgt=r7VtD;z_M-<^{ougNK?sdqDjyFIt{EU3a7dL7=n>s%L7n=Q=t+3YwfsGqDdvKL-p) zJG&~yWLRq(sZlW%w%TKKMq8#{#TpZ-C@G#bpqW4gjumYqDHGg1O5obr<_4xDg_A2I z#4sEQlz1;53|2skcNOhAm;`nb6mSW5vwJD~i2F9q!l7;g|F6K3kjt0OruZu3Sv8}O zY`E49=F_=zcBD)p6IF@-CEye~HyE5sil<@64il0gl*u|L+6*J~GVJ6-0?7u@(;qBJ zq|7$i^1h<4+A3ql8)yEt4Pqj$CK>o10=P#f?cIsJhn6&OZP!s24lN=QhH=At+`7a7 zcU8_p9-;8fl#&+Fj#tkBmG~mrP;rD=t;TD>=5xL{X_ Date: Thu, 29 Dec 2011 21:31:30 -0800 Subject: [PATCH 3/4] Fixed up memory leaks --- src/attr.c | 4 +--- src/attr_file.c | 20 ++++++++++++++------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/attr.c b/src/attr.c index fac2fa4b9..f984458d4 100644 --- a/src/attr.c +++ b/src/attr.c @@ -207,10 +207,8 @@ int git_attr_add_macro( if (error == GIT_SUCCESS) error = git_attr_cache__insert_macro(repo, macro); - if (error < GIT_SUCCESS) { + if (error < GIT_SUCCESS) git_attr_rule__free(macro); - git__free(macro); - } return error; } diff --git a/src/attr_file.c b/src/attr_file.c index 0b1eb1f67..a1379054b 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -8,6 +8,7 @@ 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) { @@ -72,7 +73,7 @@ int git_attr_file__from_buffer( /* if the rule wasn't a pattern, on to the next */ if (error != GIT_SUCCESS) { - git_attr_rule__free(rule); /* free anything partially allocated */ + git_attr_rule__clear(rule); /* reset rule contents */ if (error == GIT_ENOTFOUND) error = GIT_SUCCESS; } else { @@ -82,9 +83,8 @@ int git_attr_file__from_buffer( cleanup: if (error != GIT_SUCCESS) { - git__free(rule); + git_attr_rule__free(rule); git_attr_file__free(attrs); - git__free(attrs); } else { *out = attrs; } @@ -122,14 +122,15 @@ void git_attr_file__free(git_attr_file *file) if (!file) return; - git_vector_foreach(&file->rules, i, rule) { + 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) @@ -505,7 +506,7 @@ int git_attr_assignment__parse( return error; } -void git_attr_rule__free(git_attr_rule *rule) +static void git_attr_rule__clear(git_attr_rule *rule) { unsigned int i; git_attr_assignment *assign; @@ -525,3 +526,10 @@ void git_attr_rule__free(git_attr_rule *rule) git_vector_free(&rule->assigns); } + +void git_attr_rule__free(git_attr_rule *rule) +{ + git_attr_rule__clear(rule); + git__free(rule); +} + From bd370b14fefdba3844a9bf0bbf87171ca48f49be Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 30 Dec 2011 15:00:14 -0800 Subject: [PATCH 4/4] Improved gitattributes macro implementation This updates to implementation of gitattribute macros to be much more similar to core git (albeit not 100%) and to handle expansion of macros within macros, etc. It also cleans up the refcounting usage with macros to be much cleaner. Also, this adds a new vector function `git_vector_insert_sorted()` which allows you to maintain a sorted list as you go. In order to write that function, this changes the function `git__bsearch()` to take a somewhat different set of parameters, although the core functionality is still the same. --- src/attr_file.c | 71 +++++----- src/util.c | 36 +++-- src/util.h | 9 +- src/vector.c | 47 ++++++- src/vector.h | 3 + tests-clay/attr/repo.c | 54 +++++++- tests-clay/clay.h | 4 + tests-clay/clay_main.c | 14 +- tests-clay/core/vector.c | 125 ++++++++++++++++++ tests/resources/attr/.gitted/index | Bin 1304 -> 1376 bytes tests/resources/attr/.gitted/logs/HEAD | Bin 332 -> 491 bytes .../attr/.gitted/logs/refs/heads/master | Bin 332 -> 491 bytes .../2b/40c5aca159b04ea8d20ffe36cdf8b09369b14a | Bin 0 -> 269 bytes .../58/19a185d77b03325aaf87cafc771db36f6ddca7 | Bin 0 -> 19 bytes .../a5/d76cad53f66f1312bd995909a5bab3c0820770 | Bin 0 -> 163 bytes .../d5/7da33c16b14326ecb05d19bbea908f5e4c47d9 | Bin 0 -> 379 bytes .../resources/attr/.gitted/refs/heads/master | Bin 41 -> 41 bytes tests/resources/attr/gitattributes | Bin 162 -> 602 bytes tests/resources/attr/macro_bad | Bin 0 -> 4 bytes 19 files changed, 297 insertions(+), 66 deletions(-) create mode 100644 tests/resources/attr/.gitted/objects/2b/40c5aca159b04ea8d20ffe36cdf8b09369b14a create mode 100644 tests/resources/attr/.gitted/objects/58/19a185d77b03325aaf87cafc771db36f6ddca7 create mode 100644 tests/resources/attr/.gitted/objects/a5/d76cad53f66f1312bd995909a5bab3c0820770 create mode 100644 tests/resources/attr/.gitted/objects/d5/7da33c16b14326ecb05d19bbea908f5e4c47d9 create mode 100644 tests/resources/attr/macro_bad diff --git a/src/attr_file.c b/src/attr_file.c index a1379054b..fe8844e2d 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -12,17 +12,9 @@ static void git_attr_rule__clear(git_attr_rule *rule); int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro) { - unsigned int i; - git_attr_assignment *assign; - if (macro->assigns.length == 0) return git__throw(GIT_EMISSINGOBJDATA, "git attribute macro with no values"); - git_vector_foreach(¯o->assigns, i, assign) { - GIT_REFCOUNT_OWN(assign, macro); - GIT_REFCOUNT_INC(assign); - } - return git_hashtable_insert( repo->attrcache.macros, macro->match.pattern, macro); } @@ -358,7 +350,7 @@ static int sort_by_hash_and_name(const void *a_raw, const void *b_raw) return strcmp(b->name, a->name); } -static void free_assign(git_attr_assignment *assign) +static void git_attr_assignment__free(git_attr_assignment *assign) { git__free(assign->name); assign->name = NULL; @@ -371,6 +363,16 @@ static void free_assign(git_attr_assignment *assign) 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, @@ -382,6 +384,8 @@ int git_attr_assignment__parse( assert(assigns && !assigns->length); + assigns->_cmp = sort_by_hash_and_name; + while (*scan && *scan != '\n' && error == GIT_SUCCESS) { const char *name_start, *value_start; @@ -395,6 +399,7 @@ int git_attr_assignment__parse( error = GIT_ENOMEM; break; } + GIT_REFCOUNT_INC(assign); } assign->name_hash = 5381; @@ -449,8 +454,8 @@ int git_attr_assignment__parse( } } - /* expand macros (if given a repo) */ - if (repo != NULL) { + /* 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); @@ -458,30 +463,25 @@ int git_attr_assignment__parse( unsigned int i; git_attr_assignment *massign; - /* issue warning: if assign->value != GIT_ATTR_TRUE */ - - git__free(assign->name); - assign->name = NULL; - if (assign->is_allocated) { - git__free((void *)assign->value); - assign->value = NULL; - } - git_vector_foreach(¯o->assigns, i, massign) { - error = git_vector_insert(assigns, massign); - if (error != GIT_SUCCESS) - break; - GIT_REFCOUNT_INC(&massign->rc); - } + GIT_REFCOUNT_INC(massign); - /* continue to next assignment */ - continue; + 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(assigns, assign); - if (error < GIT_SUCCESS) + 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 */ @@ -490,13 +490,9 @@ int git_attr_assignment__parse( if (!assigns->length) error = git__throw(GIT_ENOTFOUND, "No attribute assignments found for rule"); - else { - assigns->_cmp = sort_by_hash_and_name; - git_vector_sort(assigns); - } if (assign != NULL) - free_assign(assign); + git_attr_assignment__free(assign); while (*scan && *scan != '\n') scan++; if (*scan == '\n') scan++; @@ -518,11 +514,8 @@ static void git_attr_rule__clear(git_attr_rule *rule) rule->match.pattern = NULL; rule->match.length = 0; - git_vector_foreach(&rule->assigns, i, assign) { - if (GIT_REFCOUNT_OWNER(assign) == rule) - GIT_REFCOUNT_OWN(assign, NULL); - GIT_REFCOUNT_DEC(assign, free_assign); - } + git_vector_foreach(&rule->assigns, i, assign) + GIT_REFCOUNT_DEC(assign, git_attr_assignment__free); git_vector_free(&rule->assigns); } 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 e745d77dd..593d037d4 100644 --- a/src/vector.c +++ b/src/vector.c @@ -74,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); @@ -87,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); @@ -97,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 4c053e6ae..9ee3c9ed5 100644 --- a/src/vector.h +++ b/src/vector.h @@ -45,6 +45,9 @@ GIT_INLINE(void *) git_vector_get(git_vector *v, unsigned int position) 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/tests-clay/attr/repo.c b/tests-clay/attr/repo.c index e80e24dbf..f87e7bf55 100644 --- a/tests-clay/attr/repo.c +++ b/tests-clay/attr/repo.c @@ -40,8 +40,11 @@ void test_attr_repo__get_one(void) { "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 }, @@ -166,21 +169,68 @@ 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] == NULL); + 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] == NULL); + 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 4a57926bf..e4a413513 100644 --- a/tests-clay/clay.h +++ b/tests-clay/clay.h @@ -68,6 +68,7 @@ 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); @@ -141,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_remotes__cleanup(void); extern void test_network_remotes__fnmatch(void); diff --git a/tests-clay/clay_main.c b/tests-clay/clay_main.c index ce881e45d..a3dce81cf 100644 --- a/tests-clay/clay_main.c +++ b/tests-clay/clay_main.c @@ -122,7 +122,8 @@ static const struct clay_func _clay_cb_attr_lookup[] = { {"simple", &test_attr_lookup__simple} }; static const struct clay_func _clay_cb_attr_repo[] = { - {"foreach", &test_attr_repo__foreach}, + {"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}, @@ -214,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} @@ -338,7 +342,7 @@ static const struct clay_suite _clay_suites[] = { "attr::repo", {"initialize", &test_attr_repo__initialize}, {"cleanup", &test_attr_repo__cleanup}, - _clay_cb_attr_repo, 5 + _clay_cb_attr_repo, 6 }, { "buf::basic", @@ -428,7 +432,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", @@ -559,7 +563,7 @@ static const struct clay_suite _clay_suites[] = { }; static size_t _clay_suite_count = 39; -static size_t _clay_callback_count = 134; +static size_t _clay_callback_count = 138; /* 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/index b/tests/resources/attr/.gitted/index index 9c5907386ff2aaed7325a3059c4256e1fee493d8..c52747e0b6c9a457a28c9b9fea098f0fa2e59c48 100644 GIT binary patch delta 609 zcmbQi^?*yo#WTp6fq{Vuhz0%r$Zi4B3^1AxByMRqX<~_?ICiPY6ZhbfnlhP%5trQ5 z$vz-CIU$H`5E^2e3IF6NjE43940eI#pfprYfPtZL2?GP;SD>5-&~Z`P4oBB4jNIV2 z;u8Nqv$H=oOwQcs#lV}MS&~>%Qk0ogT9R4}GKRtLuis2$8f=~w+i6tu7+4}C7q(un zW;Qa2THk)^Pr2;o{MlM+*aL4(!Nf|KtsTH*+!X_IxBaLG-dT!T|? z#^f_Nc zeVmwMh(qej#4Wg_K2Q8(DE@n!DcD*l4Ytv|?)j?#AZPFnLrgcmH{y6`i!{v(P2P=1b E0Pj<}ki}DppQj1H9xd1@}91j2h delta 7 OcmaFOe1>U*4<}ki}DppQj1H9xd1@}91j2h delta 7 OcmaFOe1>U*4W25m5Xi_$NJ-n}PV&a+7BAVG@ zW_FC4oPzh)=IIFJ93wM{2+piMH&Fj2TR1#OW$a)kT>(H9KVe2%1gfs82IoK_h4(JX z$oV?4`Msxwm27126xvKjt$a(86*mQbSi?>@q?muVNzY{L1?qjj_=s_z^D<*c4!)c> zY`!(rR$p*M`=73oBWzl-MbP`)i%*f5cy`?6pXt=R;%w1cF*55RVEi*gILx^XX zG{1OYTEM$m!;F*ZjJx`PV%=U3KJOk3c}{H2qoF=yuRCYckd*}krEAAoSi}?_IbwW7 Ts(x13mnnr1cE9)m@y^_p7~_N= literal 0 HcmV?d00001 diff --git a/tests/resources/attr/.gitted/objects/58/19a185d77b03325aaf87cafc771db36f6ddca7 b/tests/resources/attr/.gitted/objects/58/19a185d77b03325aaf87cafc771db36f6ddca7 new file mode 100644 index 0000000000000000000000000000000000000000..fe34eb63a24a5a6f018ec69a08111506d58efe31 GIT binary patch literal 19 acmbMEHKeni? RGgL~w!Q?TA_yRN8N?76vP4)l) literal 0 HcmV?d00001 diff --git a/tests/resources/attr/.gitted/objects/d5/7da33c16b14326ecb05d19bbea908f5e4c47d9 b/tests/resources/attr/.gitted/objects/d5/7da33c16b14326ecb05d19bbea908f5e4c47d9 new file mode 100644 index 0000000000000000000000000000000000000000..b96d40c24ed941d514f427c31ee6d382a7ee0286 GIT binary patch literal 379 zcmV->0fhc|0V^p=O;s>8Fk>(@FfcPQQAjK)DKcOP&F^Wd?(mk!9N4*WOR^9COxTOtMRg|A! z5)V>j$lxj>Rk^&v>FBIvr76rxTz=lq{cO=y84;z*m?%{ydLL(xjBkB8K@}xYJ7SKI0CXzW&_0iQ94~^f28-R$#=Ssd-OB Z=jF_ofz9h~9dng6ZV8>^4FLUm%U%!i#s&ZY literal 0 HcmV?d00001 diff --git a/tests/resources/attr/.gitted/refs/heads/master b/tests/resources/attr/.gitted/refs/heads/master index 1049fe4b741ea8047c4a5860a936a01c3317b93a..0516af2d228cd82542634c323befefb9f6ced7a7 100644 GIT binary patch literal 41 vcmV~$$qfK72m`Qxr%6G9G7g8>e}vk}aqArFn(fsqH4zL%va+=C1nxK=@UaTX literal 41 ucmV~$(E$J;1O>qVrV$<)hh+Z|x--JroaOVB_jC&*L(qa-WEm`)-o8HM_6o!R diff --git a/tests/resources/attr/gitattributes b/tests/resources/attr/gitattributes index 94da4faa0a6bfb8ee6ccf7153801a69202b31857..2b40c5aca159b04ea8d20ffe36cdf8b09369b14a 100644 GIT binary patch literal 602 zcmZut%Wi`(5Ip0r*rleXQD+Hb$&CCuy13AaY zOd^6SE6*LYf5{$BPjb+9)~jj&poyO_BW(hQv1w-KU_c7*x2Pk{eP#W7PY;RgWbjnl zLPxE9%aVk<3O}u3C!BK3zuTl|()I@Jv0uE$xDT@#9Ld3_^P2TTL2K0w6FUAhNzSlq z(K=of$;(CTDKVoo3-^|r-(Asl}317cCy AJ^%m! delta 9 Qcmcb`vWRg)?8Gmw02FHkIRF3v diff --git a/tests/resources/attr/macro_bad b/tests/resources/attr/macro_bad new file mode 100644 index 0000000000000000000000000000000000000000..5819a185d77b03325aaf87cafc771db36f6ddca7 GIT binary patch literal 4 LcmYew&*uUF1Hl1H literal 0 HcmV?d00001