summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/git2/attr.h80
-rw-r--r--src/attr.c400
-rw-r--r--src/attr_file.c528
-rw-r--r--src/attr_file.h110
-rw-r--r--src/config.c79
-rw-r--r--src/config.h1
-rw-r--r--src/fileops.c131
-rw-r--r--src/fileops.h24
-rw-r--r--src/hashtable.c14
-rw-r--r--src/hashtable.h7
-rw-r--r--src/refs.c15
-rw-r--r--src/repository.c1
-rw-r--r--src/repository.h2
-rw-r--r--src/util.c38
-rw-r--r--src/util.h9
-rw-r--r--src/vector.c52
-rw-r--r--src/vector.h8
-rw-r--r--src/win32/utf-conv.c5
-rw-r--r--src/win32/utf-conv.h1
-rw-r--r--tests-clay/attr/file.c223
-rw-r--r--tests-clay/attr/lookup.c257
-rw-r--r--tests-clay/attr/repo.c236
-rw-r--r--tests-clay/clay.h20
-rw-r--r--tests-clay/clay_main.c50
-rw-r--r--tests-clay/core/vector.c125
-rw-r--r--tests/resources/attr/.gitted/HEAD1
-rw-r--r--tests/resources/attr/.gitted/config6
-rw-r--r--tests/resources/attr/.gitted/description1
-rw-r--r--tests/resources/attr/.gitted/indexbin0 -> 1376 bytes
-rw-r--r--tests/resources/attr/.gitted/info/attributes2
-rw-r--r--tests/resources/attr/.gitted/info/exclude6
-rw-r--r--tests/resources/attr/.gitted/logs/HEAD3
-rw-r--r--tests/resources/attr/.gitted/logs/refs/heads/master3
-rw-r--r--tests/resources/attr/.gitted/objects/29/29de282ce999e95183aedac6451d3384559c4bbin0 -> 58 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/2b/40c5aca159b04ea8d20ffe36cdf8b09369b14a1
-rw-r--r--tests/resources/attr/.gitted/objects/2c/66e14f77196ea763fb1e41612c1aa2bc2d8ed2bin0 -> 316 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/2d/e7dfe3588f3c7e9ad59e7d50ba90e3329df9d9bin0 -> 124 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/3b/74db7ab381105dc0d28f8295a77f6a82989292bin0 -> 276 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/3e/42ffc54a663f9401cc25843d6c0e71a33e4249bin0 -> 596 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/45/141a79a77842c59a63229403220a4e4be74e3dbin0 -> 36 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/55/6f8c827b8e4a02ad5cab77dca2bcb3e226b0b3bin0 -> 24 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/58/19a185d77b03325aaf87cafc771db36f6ddca7bin0 -> 19 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/60/5812ab7fe421fdd325a935d35cb06a9234a7d72
-rw-r--r--tests/resources/attr/.gitted/objects/6b/ab5c79cd5140d0f800917f550eb2a3dc32b0da3
-rw-r--r--tests/resources/attr/.gitted/objects/94/da4faa0a6bfb8ee6ccf7153801a69202b31857bin0 -> 124 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/99/eae476896f4907224978b88e5ecaa6c5bb67a9bin0 -> 95 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/9f/b40b6675dde60b5697afceae91b66d908c02d9bin0 -> 151 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/a5/6bbcecaeac760cc26239384d2d4c614e7e4320bin0 -> 351 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/a5/d76cad53f66f1312bd995909a5bab3c08207704
-rw-r--r--tests/resources/attr/.gitted/objects/c0/091889c0c77142b87a1fa5123a6398a61d33e7bin0 -> 290 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/c4/85abe35abd4aa6fd83b076a78bbea9e2e7e06cbin0 -> 129 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/c7/aadd770d5907a8475c29e9ee21a27b88bf675dbin0 -> 60 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/d5/7da33c16b14326ecb05d19bbea908f5e4c47d9bin0 -> 379 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/d8/00886d9c86731ae5c4a62b0b77c437015e00d2bin0 -> 18 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/dc/cada462d3df8ac6de596fb8c896aba9344f941bin0 -> 35 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/e5/63cf4758f0d646f1b14b76016aa17fa9e549a4bin0 -> 39 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/f2/c6d717cf4a5a3e6b02684155ab07b766982165bin0 -> 44 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/fb/5067b1aef3ac1ada4b379dbcb7d17255df7d78bin0 -> 28 bytes
-rw-r--r--tests/resources/attr/.gitted/objects/ff/69f8639ce2e6010b3f33a74160aad98b48da2bbin0 -> 18 bytes
-rw-r--r--tests/resources/attr/.gitted/refs/heads/master1
-rw-r--r--tests/resources/attr/attr01
-rw-r--r--tests/resources/attr/attr129
-rw-r--r--tests/resources/attr/attr221
-rw-r--r--tests/resources/attr/attr34
-rw-r--r--tests/resources/attr/binfile1
-rw-r--r--tests/resources/attr/gitattributes24
-rw-r--r--tests/resources/attr/macro_bad1
-rw-r--r--tests/resources/attr/macro_test1
-rw-r--r--tests/resources/attr/root_test11
-rw-r--r--tests/resources/attr/root_test21
-rw-r--r--tests/resources/attr/root_test31
-rw-r--r--tests/resources/attr/root_test4.txt1
-rw-r--r--tests/resources/attr/subdir/.gitattributes5
-rw-r--r--tests/resources/attr/subdir/abc37
-rw-r--r--tests/resources/attr/subdir/subdir_test12
-rw-r--r--tests/resources/attr/subdir/subdir_test2.txt1
-rw-r--r--tests/resources/attr/subdir2/subdir2_test11
77 files changed, 2470 insertions, 111 deletions
diff --git a/include/git2/attr.h b/include/git2/attr.h
new file mode 100644
index 000000000..f4c5975a6
--- /dev/null
+++ b/include/git2/attr.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2009-2011 the libgit2 contributors
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_git_attr_h__
+#define INCLUDE_git_attr_h__
+
+#include "common.h"
+#include "types.h"
+
+/**
+ * @file git2/attr.h
+ * @brief Git attribute management routines
+ * @defgroup git_attr Git attribute management routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+#define GIT_ATTR_TRUE git_attr__true
+#define GIT_ATTR_FALSE git_attr__false
+#define GIT_ATTR_UNSPECIFIED NULL
+
+GIT_EXTERN(const char *)git_attr__true;
+GIT_EXTERN(const char *)git_attr__false;
+
+
+/**
+ * Lookup attribute for path returning string caller must free
+ */
+GIT_EXTERN(int) git_attr_get(
+ git_repository *repo, const char *path, const char *name,
+ const char **value);
+
+/**
+ * Lookup list of attributes for path, populating array of strings
+ */
+GIT_EXTERN(int) git_attr_get_many(
+ git_repository *repo, const char *path,
+ size_t num_attr, const char **names,
+ const char **values);
+
+/**
+ * Perform an operation on each attribute of a path.
+ */
+GIT_EXTERN(int) git_attr_foreach(
+ git_repository *repo, const char *path,
+ int (*callback)(const char *name, const char *value, void *payload),
+ void *payload);
+
+/**
+ * Flush the gitattributes cache.
+ *
+ * Call this if you have reason to believe that the attributes files
+ * on disk no longer match the cached contents of memory.
+ */
+GIT_EXTERN(void) git_attr_cache_flush(
+ git_repository *repo);
+
+/**
+ * Add a macro definition.
+ *
+ * Macros will automatically be loaded from the top level .gitattributes
+ * file of the repository (plus the build-in "binary" macro). This
+ * function allows you to add others. For example, to add the default
+ * macro, you would call:
+ *
+ * git_attr_add_macro(repo, "binary", "-diff -crlf");
+ */
+GIT_EXTERN(int) git_attr_add_macro(
+ git_repository *repo,
+ const char *name,
+ const char *values);
+
+/** @} */
+GIT_END_DECL
+#endif
+
diff --git a/src/attr.c b/src/attr.c
new file mode 100644
index 000000000..f984458d4
--- /dev/null
+++ b/src/attr.c
@@ -0,0 +1,400 @@
+#include "repository.h"
+#include "fileops.h"
+#include "config.h"
+#include <ctype.h>
+
+#define GIT_ATTR_FILE_INREPO "info/attributes"
+#define GIT_ATTR_FILE ".gitattributes"
+#define GIT_ATTR_FILE_SYSTEM "gitattributes"
+
+static int collect_attr_files(
+ git_repository *repo, const char *path, git_vector *files);
+
+static int attr_cache_init(git_repository *repo);
+
+
+int git_attr_get(
+ git_repository *repo, const char *pathname,
+ const char *name, const char **value)
+{
+ int error;
+ git_attr_path path;
+ git_vector files = GIT_VECTOR_INIT;
+ unsigned int i, j;
+ git_attr_file *file;
+ git_attr_name attr;
+ git_attr_rule *rule;
+
+ *value = NULL;
+
+ if ((error = git_attr_path__init(&path, pathname)) < GIT_SUCCESS ||
+ (error = collect_attr_files(repo, pathname, &files)) < GIT_SUCCESS)
+ return git__rethrow(error, "Could not get attribute for %s", pathname);
+
+ attr.name = name;
+ attr.name_hash = git_attr_file__name_hash(name);
+
+ git_vector_foreach(&files, i, file) {
+
+ git_attr_file__foreach_matching_rule(file, &path, j, rule) {
+ int pos = git_vector_bsearch(&rule->assigns, &attr);
+ git_clearerror(); /* okay if search failed */
+
+ if (pos >= 0) {
+ *value = ((git_attr_assignment *)git_vector_get(
+ &rule->assigns, pos))->value;
+ goto found;
+ }
+ }
+ }
+
+found:
+ git_vector_free(&files);
+
+ return error;
+}
+
+
+typedef struct {
+ git_attr_name name;
+ git_attr_assignment *found;
+} attr_get_many_info;
+
+int git_attr_get_many(
+ git_repository *repo, const char *pathname,
+ size_t num_attr, const char **names, const char **values)
+{
+ int error;
+ git_attr_path path;
+ git_vector files = GIT_VECTOR_INIT;
+ unsigned int i, j, k;
+ git_attr_file *file;
+ git_attr_rule *rule;
+ attr_get_many_info *info = NULL;
+ size_t num_found = 0;
+
+ memset(values, 0, sizeof(const char *) * num_attr);
+
+ if ((error = git_attr_path__init(&path, pathname)) < GIT_SUCCESS ||
+ (error = collect_attr_files(repo, pathname, &files)) < GIT_SUCCESS)
+ return git__rethrow(error, "Could not get attributes for %s", pathname);
+
+ if ((info = git__calloc(num_attr, sizeof(attr_get_many_info))) == NULL) {
+ git__rethrow(GIT_ENOMEM, "Could not get attributes for %s", pathname);
+ goto cleanup;
+ }
+
+ git_vector_foreach(&files, i, file) {
+
+ git_attr_file__foreach_matching_rule(file, &path, j, rule) {
+
+ for (k = 0; k < num_attr; k++) {
+ int pos;
+
+ if (info[k].found != NULL) /* already found assignment */
+ continue;
+
+ if (!info[k].name.name) {
+ info[k].name.name = names[k];
+ info[k].name.name_hash = git_attr_file__name_hash(names[k]);
+ }
+
+ pos = git_vector_bsearch(&rule->assigns, &info[k].name);
+ git_clearerror(); /* okay if search failed */
+
+ if (pos >= 0) {
+ info[k].found = (git_attr_assignment *)
+ git_vector_get(&rule->assigns, pos);
+ values[k] = info[k].found->value;
+
+ if (++num_found == num_attr)
+ goto cleanup;
+ }
+ }
+ }
+ }
+
+cleanup:
+ git_vector_free(&files);
+ git__free(info);
+
+ return error;
+}
+
+
+int git_attr_foreach(
+ git_repository *repo, const char *pathname,
+ int (*callback)(const char *name, const char *value, void *payload),
+ void *payload)
+{
+ int error;
+ git_attr_path path;
+ git_vector files = GIT_VECTOR_INIT;
+ unsigned int i, j, k;
+ git_attr_file *file;
+ git_attr_rule *rule;
+ git_attr_assignment *assign;
+ git_hashtable *seen = NULL;
+
+ if ((error = git_attr_path__init(&path, pathname)) < GIT_SUCCESS ||
+ (error = collect_attr_files(repo, pathname, &files)) < GIT_SUCCESS)
+ return git__rethrow(error, "Could not get attributes for %s", pathname);
+
+ seen = git_hashtable_alloc(8, git_hash__strhash_cb, git_hash__strcmp_cb);
+ if (!seen) {
+ error = GIT_ENOMEM;
+ goto cleanup;
+ }
+
+ git_vector_foreach(&files, i, file) {
+
+ git_attr_file__foreach_matching_rule(file, &path, j, rule) {
+
+ git_vector_foreach(&rule->assigns, k, assign) {
+ /* skip if higher priority assignment was already seen */
+ if (git_hashtable_lookup(seen, assign->name))
+ continue;
+
+ error = git_hashtable_insert(seen, assign->name, assign);
+ if (error != GIT_SUCCESS)
+ goto cleanup;
+
+ error = callback(assign->name, assign->value, payload);
+ if (error != GIT_SUCCESS)
+ goto cleanup;
+ }
+ }
+ }
+
+cleanup:
+ if (seen)
+ git_hashtable_free(seen);
+ git_vector_free(&files);
+
+ if (error != GIT_SUCCESS)
+ (void)git__rethrow(error, "Could not get attributes for %s", pathname);
+
+ return error;
+}
+
+
+int git_attr_add_macro(
+ git_repository *repo,
+ const char *name,
+ const char *values)
+{
+ int error;
+ git_attr_rule *macro = NULL;
+
+ if ((error = attr_cache_init(repo)) < GIT_SUCCESS)
+ return error;
+
+ macro = git__calloc(1, sizeof(git_attr_rule));
+ if (!macro)
+ return GIT_ENOMEM;
+
+ macro->match.pattern = git__strdup(name);
+ if (!macro->match.pattern) {
+ git__free(macro);
+ return GIT_ENOMEM;
+ }
+
+ macro->match.length = strlen(macro->match.pattern);
+ macro->match.flags = GIT_ATTR_FNMATCH_MACRO;
+
+ error = git_attr_assignment__parse(repo, &macro->assigns, &values);
+
+ if (error == GIT_SUCCESS)
+ error = git_attr_cache__insert_macro(repo, macro);
+
+ if (error < GIT_SUCCESS)
+ git_attr_rule__free(macro);
+
+ return error;
+}
+
+
+/* add git_attr_file to vector of files, loading if needed */
+static int push_attrs(
+ git_repository *repo,
+ git_vector *files,
+ const char *base,
+ const char *filename)
+{
+ int error = GIT_SUCCESS;
+ git_attr_cache *cache = &repo->attrcache;
+ git_buf path = GIT_BUF_INIT;
+ git_attr_file *file;
+ int add_to_cache = 0;
+
+ if ((error = git_path_prettify(&path, filename, base)) < GIT_SUCCESS) {
+ if (error == GIT_EOSERR)
+ /* file was not found -- ignore error */
+ error = GIT_SUCCESS;
+ goto cleanup;
+ }
+
+ /* either get attr_file from cache or read from disk */
+ file = git_hashtable_lookup(cache->files, path.ptr);
+ if (file == NULL) {
+ error = git_attr_file__from_file(repo, path.ptr, &file);
+ add_to_cache = (error == GIT_SUCCESS);
+ }
+
+ if (file != NULL) {
+ /* add file to vector, if we found it */
+ error = git_vector_insert(files, file);
+
+ /* add file to cache, if it is new */
+ /* do this after above step b/c it is not critical */
+ if (error == GIT_SUCCESS && add_to_cache && file->path != NULL)
+ error = git_hashtable_insert(cache->files, file->path, file);
+ }
+
+cleanup:
+ git_buf_free(&path);
+ return error;
+}
+
+
+static int collect_attr_files(
+ git_repository *repo, const char *path, git_vector *files)
+{
+ int error = GIT_SUCCESS;
+ git_buf dir = GIT_BUF_INIT;
+ git_config *cfg;
+ const char *workdir = git_repository_workdir(repo);
+
+ if ((error = attr_cache_init(repo)) < GIT_SUCCESS)
+ goto cleanup;
+
+ if ((error = git_vector_init(files, 4, NULL)) < GIT_SUCCESS)
+ goto cleanup;
+
+ if ((error = git_path_prettify(&dir, path, workdir)) < GIT_SUCCESS)
+ goto cleanup;
+
+ if (git_futils_isdir(dir.ptr) != GIT_SUCCESS) {
+ git_path_dirname_r(&dir, dir.ptr);
+ git_path_to_dir(&dir);
+ if ((error = git_buf_lasterror(&dir)) < GIT_SUCCESS)
+ goto cleanup;
+ }
+
+ /* in precendence order highest to lowest:
+ * - $GIT_DIR/info/attributes
+ * - path components with .gitattributes
+ * - config core.attributesfile
+ * - $GIT_PREFIX/etc/gitattributes
+ */
+
+ error = push_attrs(repo, files, repo->path_repository, GIT_ATTR_FILE_INREPO);
+ if (error < GIT_SUCCESS)
+ goto cleanup;
+
+ if (workdir && git__prefixcmp(dir.ptr, workdir) == 0) {
+ ssize_t rootlen = (ssize_t)strlen(workdir);
+
+ do {
+ error = push_attrs(repo, files, dir.ptr, GIT_ATTR_FILE);
+ if (error == GIT_SUCCESS) {
+ git_path_dirname_r(&dir, dir.ptr);
+ git_path_to_dir(&dir);
+ error = git_buf_lasterror(&dir);
+ }
+ } while (!error && dir.size >= rootlen);
+ } else {
+ error = push_attrs(repo, files, dir.ptr, GIT_ATTR_FILE);
+ }
+ if (error < GIT_SUCCESS)
+ goto cleanup;
+
+ if (git_repository_config(&cfg, repo) == GIT_SUCCESS) {
+ const char *core_attribs = NULL;
+ git_config_get_string(cfg, "core.attributesfile", &core_attribs);
+ git_clearerror(); /* don't care if attributesfile is not set */
+ if (core_attribs)
+ error = push_attrs(repo, files, NULL, core_attribs);
+ git_config_free(cfg);
+ }
+
+ if (error == GIT_SUCCESS) {
+ error = git_futils_find_system_file(&dir, GIT_ATTR_FILE_SYSTEM);
+ if (error == GIT_SUCCESS)
+ error = push_attrs(repo, files, NULL, dir.ptr);
+ else if (error == GIT_ENOTFOUND)
+ error = GIT_SUCCESS;
+ }
+
+ cleanup:
+ if (error < GIT_SUCCESS) {
+ git__rethrow(error, "Could not get attributes for '%s'", path);
+ git_vector_free(files);
+ }
+ git_buf_free(&dir);
+
+ return error;
+}
+
+
+static int attr_cache_init(git_repository *repo)
+{
+ int error = GIT_SUCCESS;
+ git_attr_cache *cache = &repo->attrcache;
+
+ if (cache->initialized)
+ return GIT_SUCCESS;
+
+ if (cache->files == NULL) {
+ cache->files = git_hashtable_alloc(
+ 8, git_hash__strhash_cb, git_hash__strcmp_cb);
+ if (!cache->files)
+ return git__throw(GIT_ENOMEM, "Could not initialize attribute cache");
+ }
+
+ if (cache->macros == NULL) {
+ cache->macros = git_hashtable_alloc(
+ 8, git_hash__strhash_cb, git_hash__strcmp_cb);
+ if (!cache->macros)
+ return git__throw(GIT_ENOMEM, "Could not initialize attribute cache");
+ }
+
+ cache->initialized = 1;
+
+ /* insert default macros */
+ error = git_attr_add_macro(repo, "binary", "-diff -crlf");
+
+ return error;
+}
+
+
+void git_attr_cache_flush(
+ git_repository *repo)
+{
+ if (!repo)
+ return;
+
+ if (repo->attrcache.files) {
+ const void *GIT_UNUSED(name);
+ git_attr_file *file;
+
+ GIT_HASHTABLE_FOREACH(repo->attrcache.files, name, file,
+ git_attr_file__free(file));
+
+ git_hashtable_free(repo->attrcache.files);
+ repo->attrcache.files = NULL;
+ }
+
+ if (repo->attrcache.macros) {
+ const void *GIT_UNUSED(name);
+ git_attr_rule *rule;
+
+ GIT_HASHTABLE_FOREACH(repo->attrcache.macros, name, rule,
+ git_attr_rule__free(rule));
+
+ git_hashtable_free(repo->attrcache.macros);
+ repo->attrcache.macros = NULL;
+ }
+
+ repo->attrcache.initialized = 0;
+}
diff --git a/src/attr_file.c b/src/attr_file.c
new file mode 100644
index 000000000..fe8844e2d
--- /dev/null
+++ b/src/attr_file.c
@@ -0,0 +1,528 @@
+#include "common.h"
+#include "repository.h"
+#include "filebuf.h"
+#include <ctype.h>
+
+const char *git_attr__true = "[internal]__TRUE__";
+const char *git_attr__false = "[internal]__FALSE__";
+
+static int git_attr_fnmatch__parse(git_attr_fnmatch *spec, const char **base);
+static int sort_by_hash_and_name(const void *a_raw, const void *b_raw);
+static void git_attr_rule__clear(git_attr_rule *rule);
+
+int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro)
+{
+ if (macro->assigns.length == 0)
+ return git__throw(GIT_EMISSINGOBJDATA, "git attribute macro with no values");
+
+ return git_hashtable_insert(
+ repo->attrcache.macros, macro->match.pattern, macro);
+}
+
+int git_attr_file__from_buffer(
+ git_repository *repo, const char *buffer, git_attr_file **out)
+{
+ int error = GIT_SUCCESS;
+ git_attr_file *attrs = NULL;
+ const char *scan = NULL;
+ git_attr_rule *rule = NULL;
+
+ *out = NULL;
+
+ attrs = git__calloc(1, sizeof(git_attr_file));
+ if (attrs == NULL)
+ return git__throw(GIT_ENOMEM, "Could not allocate attribute storage");
+
+ attrs->path = NULL;
+
+ error = git_vector_init(&attrs->rules, 4, NULL);
+ if (error != GIT_SUCCESS) {
+ git__rethrow(error, "Could not initialize attribute storage");
+ goto cleanup;
+ }
+
+ scan = buffer;
+
+ while (error == GIT_SUCCESS && *scan) {
+ /* allocate rule if needed */
+ if (!rule && !(rule = git__calloc(1, sizeof(git_attr_rule)))) {
+ error = GIT_ENOMEM;
+ break;
+ }
+
+ /* parse the next "pattern attr attr attr" line */
+ if (!(error = git_attr_fnmatch__parse(&rule->match, &scan)) &&
+ !(error = git_attr_assignment__parse(repo, &rule->assigns, &scan)))
+ {
+ if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO)
+ /* should generate error/warning if this is coming from any
+ * file other than .gitattributes at repo root.
+ */
+ error = git_attr_cache__insert_macro(repo, rule);
+ else
+ error = git_vector_insert(&attrs->rules, rule);
+ }
+
+ /* if the rule wasn't a pattern, on to the next */
+ if (error != GIT_SUCCESS) {
+ git_attr_rule__clear(rule); /* reset rule contents */
+ if (error == GIT_ENOTFOUND)
+ error = GIT_SUCCESS;
+ } else {
+ rule = NULL; /* vector now "owns" the rule */
+ }
+ }
+
+cleanup:
+ if (error != GIT_SUCCESS) {
+ git_attr_rule__free(rule);
+ git_attr_file__free(attrs);
+ } else {
+ *out = attrs;
+ }
+
+ return error;
+}
+
+int git_attr_file__from_file(
+ git_repository *repo, const char *path, git_attr_file **out)
+{
+ int error = GIT_SUCCESS;
+ git_fbuffer fbuf = GIT_FBUFFER_INIT;
+
+ *out = NULL;
+
+ if ((error = git_futils_readbuffer(&fbuf, path)) < GIT_SUCCESS ||
+ (error = git_attr_file__from_buffer(repo, fbuf.data, out)) < GIT_SUCCESS)
+ {
+ git__rethrow(error, "Could not open attribute file '%s'", path);
+ } else {
+ /* save path (okay to fail) */
+ (*out)->path = git__strdup(path);
+ }
+
+ git_futils_freebuffer(&fbuf);
+
+ return error;
+}
+
+void git_attr_file__free(git_attr_file *file)
+{
+ unsigned int i;
+ git_attr_rule *rule;
+
+ if (!file)
+ return;
+
+ git_vector_foreach(&file->rules, i, rule)
+ git_attr_rule__free(rule);
+
+ git_vector_free(&file->rules);
+
+ git__free(file->path);
+ file->path = NULL;
+
+ git__free(file);
+}
+
+unsigned long git_attr_file__name_hash(const char *name)
+{
+ unsigned long h = 5381;
+ int c;
+ assert(name);
+ while ((c = (int)*name++) != 0)
+ h = ((h << 5) + h) + c;
+ return h;
+}
+
+
+int git_attr_file__lookup_one(
+ git_attr_file *file,
+ const git_attr_path *path,
+ const char *attr,
+ const char **value)
+{
+ unsigned int i;
+ git_attr_name name;
+ git_attr_rule *rule;
+
+ *value = NULL;
+
+ name.name = attr;
+ name.name_hash = git_attr_file__name_hash(attr);
+
+ git_attr_file__foreach_matching_rule(file, path, i, rule) {
+ int pos = git_vector_bsearch(&rule->assigns, &name);
+ git_clearerror(); /* okay if search failed */
+
+ if (pos >= 0) {
+ *value = ((git_attr_assignment *)
+ git_vector_get(&rule->assigns, pos))->value;
+ break;
+ }
+ }
+
+ return GIT_SUCCESS;
+}
+
+
+int git_attr_rule__match_path(
+ git_attr_rule *rule,
+ const git_attr_path *path)
+{
+ int matched = FNM_NOMATCH;
+
+ if (rule->match.flags & GIT_ATTR_FNMATCH_DIRECTORY && !path->is_dir)
+ return matched;
+
+ if (rule->match.flags & GIT_ATTR_FNMATCH_FULLPATH)
+ matched = p_fnmatch(rule->match.pattern, path->path, FNM_PATHNAME);
+ else
+ matched = p_fnmatch(rule->match.pattern, path->basename, 0);
+
+ if (rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE)
+ matched = (matched == GIT_SUCCESS) ? FNM_NOMATCH : GIT_SUCCESS;
+
+ return matched;
+}
+
+git_attr_assignment *git_attr_rule__lookup_assignment(
+ git_attr_rule *rule, const char *name)
+{
+ int pos;
+ git_attr_name key;
+ key.name = name;
+ key.name_hash = git_attr_file__name_hash(name);
+
+ pos = git_vector_bsearch(&rule->assigns, &key);
+ git_clearerror(); /* okay if search failed */
+
+ return (pos >= 0) ? git_vector_get(&rule->assigns, pos) : NULL;
+}
+
+int git_attr_path__init(
+ git_attr_path *info, const char *path)
+{
+ info->path = path;
+ info->basename = strrchr(path, '/');
+ if (info->basename)
+ info->basename++;
+ if (!info->basename || !*info->basename)
+ info->basename = path;
+ info->is_dir = (git_futils_isdir(path) == GIT_SUCCESS);
+ return GIT_SUCCESS;
+}
+
+
+/*
+ * From gitattributes(5):
+ *
+ * Patterns have the following format:
+ *
+ * - A blank line matches no files, so it can serve as a separator for
+ * readability.
+ *
+ * - A line starting with # serves as a comment.
+ *
+ * - An optional prefix ! which negates the pattern; any matching file
+ * excluded by a previous pattern will become included again. If a negated
+ * pattern matches, this will override lower precedence patterns sources.
+ *
+ * - If the pattern ends with a slash, it is removed for the purpose of the
+ * following description, but it would only find a match with a directory. In
+ * other words, foo/ will match a directory foo and paths underneath it, but
+ * will not match a regular file or a symbolic link foo (this is consistent
+ * with the way how pathspec works in general in git).
+ *
+ * - If the pattern does not contain a slash /, git treats it as a shell glob
+ * pattern and checks for a match against the pathname without leading
+ * directories.
+ *
+ * - Otherwise, git treats the pattern as a shell glob suitable for consumption
+ * by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will
+ * not match a / in the pathname. For example, "Documentation/\*.html" matches
+ * "Documentation/git.html" but not "Documentation/ppc/ppc.html". A leading
+ * slash matches the beginning of the pathname; for example, "/\*.c" matches
+ * "cat-file.c" but not "mozilla-sha1/sha1.c".
+ */
+
+/*
+ * This will return GIT_SUCCESS if the spec was filled out,
+ * GIT_ENOTFOUND if the fnmatch does not require matching, or
+ * another error code there was an actual problem.
+ */
+static int git_attr_fnmatch__parse(
+ git_attr_fnmatch *spec,
+ const char **base)
+{
+ const char *pattern;
+ const char *scan;
+ int slash_count;
+ int error = GIT_SUCCESS;
+
+ assert(base && *base);
+
+ pattern = *base;
+
+ while (isspace(*pattern)) pattern++;
+ if (!*pattern || *pattern == '#') {
+ error = GIT_ENOTFOUND;
+ goto skip_to_eol;
+ }
+
+ spec->flags = 0;
+
+ if (*pattern == '[') {
+ if (strncmp(pattern, "[attr]", 6) == 0) {
+ spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO;
+ pattern += 6;
+ } else {
+ /* unrecognized meta instructions - skip the line */
+ error = GIT_ENOTFOUND;
+ goto skip_to_eol;
+ }
+ }
+
+ if (*pattern == '!') {
+ spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE;
+ pattern++;
+ }
+
+ slash_count = 0;
+ for (scan = pattern; *scan != '\0'; ++scan) {
+ if (isspace(*scan) && *(scan - 1) != '\\')
+ break;
+
+ if (*scan == '/') {
+ spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH;
+ slash_count++;
+ }
+ }
+
+ *base = scan;
+ spec->length = scan - pattern;
+ spec->pattern = git__strndup(pattern, spec->length);
+
+ if (!spec->pattern) {
+ error = GIT_ENOMEM;
+ goto skip_to_eol;
+ } else {
+ char *from = spec->pattern, *to = spec->pattern;
+ while (*from) {
+ if (*from == '\\') {
+ from++;
+ spec->length--;
+ }
+ *to++ = *from++;
+ }
+ *to = '\0';
+ }
+
+ if (pattern[spec->length - 1] == '/') {
+ spec->length--;
+ spec->pattern[spec->length] = '\0';
+ spec->flags = spec->flags | GIT_ATTR_FNMATCH_DIRECTORY;
+ if (--slash_count <= 0)
+ spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH;
+ }
+
+ return GIT_SUCCESS;
+
+skip_to_eol:
+ /* skip to end of line */
+ while (*pattern && *pattern != '\n') pattern++;
+ if (*pattern == '\n') pattern++;
+ *base = pattern;
+
+ return error;
+}
+
+static int sort_by_hash_and_name(const void *a_raw, const void *b_raw)
+{
+ const git_attr_name *a = a_raw;
+ const git_attr_name *b = b_raw;
+
+ if (b->name_hash < a->name_hash)
+ return 1;
+ else if (b->name_hash > a->name_hash)
+ return -1;
+ else
+ return strcmp(b->name, a->name);
+}
+
+static void git_attr_assignment__free(git_attr_assignment *assign)
+{
+ git__free(assign->name);
+ assign->name = NULL;
+
+ if (assign->is_allocated) {
+ git__free((void *)assign->value);
+ assign->value = NULL;
+ }
+
+ git__free(assign);
+}
+
+static int merge_assignments(void **old_raw, void *new_raw)
+{
+ git_attr_assignment **old = (git_attr_assignment **)old_raw;
+ git_attr_assignment *new = (git_attr_assignment *)new_raw;
+
+ GIT_REFCOUNT_DEC(*old, git_attr_assignment__free);
+ *old = new;
+ return GIT_EEXISTS;
+}
+
+int git_attr_assignment__parse(
+ git_repository *repo,
+ git_vector *assigns,
+ const char **base)
+{
+ int error = GIT_SUCCESS;
+ const char *scan = *base;
+ git_attr_assignment *assign = NULL;
+
+ assert(assigns && !assigns->length);
+
+ assigns->_cmp = sort_by_hash_and_name;
+
+ while (*scan && *scan != '\n' && error == GIT_SUCCESS) {
+ const char *name_start, *value_start;
+
+ /* skip leading blanks */
+ while (isspace(*scan) && *scan != '\n') scan++;
+
+ /* allocate assign if needed */
+ if (!assign) {
+ assign = git__calloc(1, sizeof(git_attr_assignment));
+ if (!assign) {
+ error = GIT_ENOMEM;
+ break;
+ }
+ GIT_REFCOUNT_INC(assign);
+ }
+
+ assign->name_hash = 5381;
+ assign->value = GIT_ATTR_TRUE;
+ assign->is_allocated = 0;
+
+ /* look for magic name prefixes */
+ if (*scan == '-') {
+ assign->value = GIT_ATTR_FALSE;
+ scan++;
+ } else if (*scan == '!') {
+ assign->value = NULL; /* explicit unspecified state */
+ scan++;
+ } else if (*scan == '#') /* comment rest of line */
+ break;
+
+ /* find the name */
+ name_start = scan;
+ while (*scan && !isspace(*scan) && *scan != '=') {
+ assign->name_hash =
+ ((assign->name_hash << 5) + assign->name_hash) + *scan;
+ scan++;
+ }
+ if (scan == name_start) {
+ /* must have found lone prefix (" - ") or leading = ("=foo")
+ * or end of buffer -- advance until whitespace and continue
+ */
+ while (*scan && !isspace(*scan)) scan++;
+ continue;
+ }
+
+ /* allocate permanent storage for name */
+ assign->name = git__strndup(name_start, scan - name_start);
+ if (!assign->name) {
+ error = GIT_ENOMEM;
+ break;
+ }
+
+ /* if there is an equals sign, find the value */
+ if (*scan == '=') {
+ for (value_start = ++scan; *scan && !isspace(*scan); ++scan);
+
+ /* if we found a value, allocate permanent storage for it */
+ if (scan > value_start) {
+ assign->value = git__strndup(value_start, scan - value_start);
+ if (!assign->value) {
+ error = GIT_ENOMEM;
+ break;
+ } else {
+ assign->is_allocated = 1;
+ }
+ }
+ }
+
+ /* expand macros (if given a repo with a macro cache) */
+ if (repo != NULL && assign->value == GIT_ATTR_TRUE) {
+ git_attr_rule *macro =
+ git_hashtable_lookup(repo->attrcache.macros, assign->name);
+
+ if (macro != NULL) {
+ unsigned int i;
+ git_attr_assignment *massign;
+
+ git_vector_foreach(&macro->assigns, i, massign) {
+ GIT_REFCOUNT_INC(massign);
+
+ error = git_vector_insert_sorted(
+ assigns, massign, &merge_assignments);
+
+ if (error == GIT_EEXISTS)
+ error = GIT_SUCCESS;
+ else if (error != GIT_SUCCESS)
+ break;
+ }
+ }
+ }
+
+ /* insert allocated assign into vector */
+ error = git_vector_insert_sorted(assigns, assign, &merge_assignments);
+ if (error == GIT_EEXISTS)
+ error = GIT_SUCCESS;
+ else if (error < GIT_SUCCESS)
+ break;
+
+ /* clear assign since it is now "owned" by the vector */
+ assign = NULL;
+ }
+
+ if (!assigns->length)
+ error = git__throw(GIT_ENOTFOUND, "No attribute assignments found for rule");
+
+ if (assign != NULL)
+ git_attr_assignment__free(assign);
+
+ while (*scan && *scan != '\n') scan++;
+ if (*scan == '\n') scan++;
+
+ *base = scan;
+
+ return error;
+}
+
+static void git_attr_rule__clear(git_attr_rule *rule)
+{
+ unsigned int i;
+ git_attr_assignment *assign;
+
+ if (!rule)
+ return;
+
+ git__free(rule->match.pattern);
+ rule->match.pattern = NULL;
+ rule->match.length = 0;
+
+ git_vector_foreach(&rule->assigns, i, assign)
+ GIT_REFCOUNT_DEC(assign, git_attr_assignment__free);
+
+ git_vector_free(&rule->assigns);
+}
+
+void git_attr_rule__free(git_attr_rule *rule)
+{
+ git_attr_rule__clear(rule);
+ git__free(rule);
+}
+
diff --git a/src/attr_file.h b/src/attr_file.h
new file mode 100644
index 000000000..bed440d61
--- /dev/null
+++ b/src/attr_file.h
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2009-2011 the libgit2 contributors
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_attr_file_h__
+#define INCLUDE_attr_file_h__
+
+#include "git2/attr.h"
+#include "vector.h"
+#include "hashtable.h"
+
+#define GIT_ATTR_FNMATCH_NEGATIVE (1U << 0)
+#define GIT_ATTR_FNMATCH_DIRECTORY (1U << 1)
+#define GIT_ATTR_FNMATCH_FULLPATH (1U << 2)
+#define GIT_ATTR_FNMATCH_MACRO (1U << 3)
+
+typedef struct {
+ char *pattern;
+ size_t length;
+ unsigned int flags;
+} git_attr_fnmatch;
+
+typedef struct {
+ git_refcount unused;
+ const char *name;
+ unsigned long name_hash;
+} git_attr_name;
+
+typedef struct {
+ git_refcount rc; /* for macros */
+ char *name;
+ unsigned long name_hash;
+ const char *value;
+ int is_allocated;
+} git_attr_assignment;
+
+typedef struct {
+ git_attr_fnmatch match;
+ git_vector assigns; /* vector of <git_attr_assignment*> */
+} git_attr_rule;
+
+typedef struct {
+ char *path; /* cache the path this was loaded from */
+ git_vector rules; /* vector of <git_attr_rule*> */
+} git_attr_file;
+
+typedef struct {
+ const char *path;
+ const char *basename;
+ int is_dir;
+} git_attr_path;
+
+typedef struct {
+ int initialized;
+ git_hashtable *files; /* hash path to git_attr_file */
+ git_hashtable *macros; /* hash name to vector<git_attr_assignment> */
+} git_attr_cache;
+
+/*
+ * git_attr_file API
+ */
+
+extern int git_attr_file__from_buffer(
+ git_repository *repo, const char *buf, git_attr_file **out);
+extern int git_attr_file__from_file(
+ git_repository *repo, const char *path, git_attr_file **out);
+
+extern void git_attr_file__free(git_attr_file *file);
+
+extern int git_attr_file__lookup_one(
+ git_attr_file *file,
+ const git_attr_path *path,
+ const char *attr,
+ const char **value);
+
+/* loop over rules in file from bottom to top */
+#define git_attr_file__foreach_matching_rule(file, path, iter, rule) \
+ git_vector_rforeach(&(file)->rules, (iter), (rule)) \
+ if (git_attr_rule__match_path((rule), (path)) == GIT_SUCCESS)
+
+extern unsigned long git_attr_file__name_hash(const char *name);
+
+
+/*
+ * other utilities
+ */
+
+extern void git_attr_rule__free(git_attr_rule *rule);
+
+extern int git_attr_rule__match_path(
+ git_attr_rule *rule,
+ const git_attr_path *path);
+
+extern git_attr_assignment *git_attr_rule__lookup_assignment(
+ git_attr_rule *rule, const char *name);
+
+extern int git_attr_path__init(
+ git_attr_path *info, const char *path);
+
+extern int git_attr_assignment__parse(
+ git_repository *repo, /* needed to expand macros */
+ git_vector *assigns,
+ const char **scan);
+
+extern int git_attr_cache__insert_macro(
+ git_repository *repo, git_attr_rule *macro);
+
+#endif
diff --git a/src/config.c b/src/config.c
index f8ff05056..1338ef3b1 100644
--- a/src/config.c
+++ b/src/config.c
@@ -337,6 +337,11 @@ int git_config_get_string(git_config *cfg, const char *name, const char **out)
return git__throw(error, "Config value '%s' not found", name);
}
+int git_config_find_global_r(git_buf *path)
+{
+ return git_futils_find_global_file(path, GIT_CONFIG_FILENAME);
+}
+
int git_config_find_global(char *global_config_path)
{
git_buf path = GIT_BUF_INIT;
@@ -354,79 +359,9 @@ int git_config_find_global(char *global_config_path)
return error;
}
-int git_config_find_global_r(git_buf *path)
+int git_config_find_system_r(git_buf *path)
{
- int error;
- const char *home = getenv("HOME");
-
-#ifdef GIT_WIN32
- if (home == NULL)
- home = getenv("USERPROFILE");
-#endif
-
- if (home == NULL)
- return git__throw(GIT_EOSERR, "Failed to open global config file. Cannot locate the user's home directory");
-
- if ((error = git_buf_joinpath(path, home, GIT_CONFIG_FILENAME)) < GIT_SUCCESS)
- return error;
-
- if (git_futils_exists(path->ptr) < GIT_SUCCESS) {
- git_buf_clear(path);
- return git__throw(GIT_EOSERR, "Failed to open global config file. The file does not exist");
- }
-
- return GIT_SUCCESS;
-}
-
-
-
-#if GIT_WIN32
-static int win32_find_system(git_buf *system_config_path)
-{
- const wchar_t *query = L"%PROGRAMFILES%\\Git\\etc\\gitconfig";
- wchar_t *apphome_utf16;
- char *apphome_utf8;
- DWORD size, ret;
-
- size = ExpandEnvironmentStringsW(query, NULL, 0);
- /* The function gave us the full size of the buffer in chars, including NUL */
- apphome_utf16 = git__malloc(size * sizeof(wchar_t));
- if (apphome_utf16 == NULL)
- return GIT_ENOMEM;
-
- ret = ExpandEnvironmentStringsW(query, apphome_utf16, size);
- if (ret != size)
- return git__throw(GIT_ERROR, "Failed to expand environment strings");
-
- if (_waccess(apphome_utf16, F_OK) < 0) {
- git__free(apphome_utf16);
- return GIT_ENOTFOUND;
- }
-
- apphome_utf8 = gitwin_from_utf16(apphome_utf16);
- git__free(apphome_utf16);
-
- git_buf_attach(system_config_path, apphome_utf8, 0);
-
- return GIT_SUCCESS;
-}
-#endif
-
-int git_config_find_system_r(git_buf *system_config_path)
-{
- if (git_buf_sets(system_config_path, "/etc/gitconfig") < GIT_SUCCESS)
- return git_buf_lasterror(system_config_path);
-
- if (git_futils_exists(system_config_path->ptr) == GIT_SUCCESS)
- return GIT_SUCCESS;
-
- git_buf_clear(system_config_path);
-
-#if GIT_WIN32
- return win32_find_system(system_config_path);
-#else
- return GIT_ENOTFOUND;
-#endif
+ return git_futils_find_system_file(path, GIT_CONFIG_FILENAME_SYSTEM);
}
int git_config_find_system(char *system_config_path)
diff --git a/src/config.h b/src/config.h
index fc639c6d4..6345b0a5d 100644
--- a/src/config.h
+++ b/src/config.h
@@ -14,6 +14,7 @@
#define GIT_CONFIG_FILENAME ".gitconfig"
#define GIT_CONFIG_FILENAME_INREPO "config"
+#define GIT_CONFIG_FILENAME_SYSTEM "gitconfig"
#define GIT_CONFIG_FILE_MODE 0666
struct git_config {
diff --git a/src/fileops.c b/src/fileops.c
index fb2f954d7..5eb7bf6ec 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -403,3 +403,134 @@ int git_futils_contains_file(git_buf *base, const char *file, int append_if_exis
return _check_dir_contents(base, file, append_if_exists, &git_futils_isfile);
}
+int git_futils_find_global_file(git_buf *path, const char *filename)
+{
+ int error;
+ const char *home = getenv("HOME");
+
+#ifdef GIT_WIN32
+ if (home == NULL)
+ home = getenv("USERPROFILE");
+#endif
+
+ if (home == NULL)
+ return git__throw(GIT_EOSERR, "Failed to open global %s file. "
+ "Cannot locate the user's home directory.", filename);
+
+ if ((error = git_buf_joinpath(path, home, filename)) < GIT_SUCCESS)
+ return error;
+
+ if (git_futils_exists(path->ptr) < GIT_SUCCESS) {
+ git_buf_clear(path);
+ return GIT_ENOTFOUND;
+ }
+
+ return GIT_SUCCESS;
+}
+
+#ifdef GIT_WIN32
+typedef struct {
+ wchar_t *path;
+ DWORD len;
+} win32_path;
+
+static const win32_path *win32_system_root(void)
+{
+ static win32_path s_root = { 0, 0 };
+
+ if (s_root.path == NULL) {
+ const wchar_t *root_tmpl = L"%PROGRAMFILES%\\Git\\etc\\";
+
+ s_root.len = ExpandEnvironmentStringsW(root_tmpl, NULL, 0);
+
+ if (s_root.len <= 0) {
+ git__throw(GIT_EOSERR, "Failed to expand environment strings");
+ return NULL;
+ }
+
+ s_root.path = git__calloc(s_root.len, sizeof(wchar_t));
+ if (s_root.path == NULL)
+ return NULL;
+
+ if (ExpandEnvironmentStringsW(root_tmpl, s_root.path, s_root.len) != s_root.len) {
+ git__throw(GIT_EOSERR, "Failed to expand environment strings");
+ git__free(s_root.path);
+ s_root.path = NULL;
+ return NULL;
+ }
+ }
+
+ return &s_root;
+}
+
+static int win32_find_system_file(git_buf *path, const char *filename)
+{
+ int error = GIT_SUCCESS;
+ const win32_path *root = win32_system_root();
+ size_t len;
+ wchar_t *file_utf16 = NULL, *scan;
+ char *file_utf8 = NULL;
+
+ if (!root || !filename || (len = strlen(filename)) == 0)
+ return GIT_ENOTFOUND;
+
+ /* allocate space for wchar_t path to file */
+ file_utf16 = git__calloc(root->len + len + 2, sizeof(wchar_t));
+ if (!file_utf16)
+ return GIT_ENOMEM;
+
+ /* append root + '\\' + filename as wchar_t */
+ memcpy(file_utf16, root->path, root->len * sizeof(wchar_t));
+
+ if (*filename == '/' || *filename == '\\')
+ filename++;
+
+ if (gitwin_append_utf16(file_utf16 + root->len - 1, filename, len + 1) !=
+ (int)len) {
+ error = git__throw(GIT_EOSERR, "Failed to build file path");
+ goto cleanup;
+ }
+
+ for (scan = file_utf16; *scan; scan++)
+ if (*scan == L'/')
+ *scan = L'\\';
+
+ /* check access */
+ if (_waccess(file_utf16, F_OK) < 0) {
+ error = GIT_ENOTFOUND;
+ goto cleanup;
+ }
+
+ /* convert to utf8 */
+ if ((file_utf8 = gitwin_from_utf16(file_utf16)) == NULL)
+ error = GIT_ENOMEM;
+
+ if (file_utf8) {
+ git_path_mkposix(file_utf8);
+ git_buf_attach(path, file_utf8, 0);
+ }
+
+cleanup:
+ git__free(file_utf16);
+
+ return error;
+}
+#endif
+
+int git_futils_find_system_file(git_buf *path, const char *filename)
+{
+ if (git_buf_joinpath(path, "/etc", filename) < GIT_SUCCESS)
+ return git_buf_lasterror(path);
+
+ if (git_futils_exists(path->ptr) == GIT_SUCCESS)
+ return GIT_SUCCESS;
+
+ git_buf_clear(path);
+
+#ifdef GIT_WIN32
+ return win32_find_system_file(path, filename);
+#else
+ return GIT_ENOTFOUND;
+#endif
+}
+
diff --git a/src/fileops.h b/src/fileops.h
index df135d0db..31f3e6a91 100644
--- a/src/fileops.h
+++ b/src/fileops.h
@@ -165,4 +165,28 @@ extern int git_futils_direach(
extern int git_futils_cmp_path(const char *name1, int len1, int isdir1,
const char *name2, int len2, int isdir2);
+/**
+ * Find a "global" file (i.e. one in a user's home directory).
+ *
+ * @param pathbuf buffer to write the full path into
+ * @param filename name of file to find in the home directory
+ * @return
+ * - GIT_SUCCESS if found;
+ * - GIT_ENOTFOUND if not found;
+ * - GIT_EOSERR on an unspecified OS related error.
+ */
+extern int git_futils_find_global_file(git_buf *path, const char *filename);
+
+/**
+ * Find a "system" file (i.e. one shared for all users of the system).
+ *
+ * @param pathbuf buffer to write the full path into
+ * @param filename name of file to find in the home directory
+ * @return
+ * - GIT_SUCCESS if found;
+ * - GIT_ENOTFOUND if not found;
+ * - GIT_EOSERR on an unspecified OS related error.
+ */
+extern int git_futils_find_system_file(git_buf *path, const char *filename);
+
#endif /* INCLUDE_fileops_h__ */
diff --git a/src/hashtable.c b/src/hashtable.c
index 15d173992..f836f166d 100644
--- a/src/hashtable.c
+++ b/src/hashtable.c
@@ -241,3 +241,17 @@ int git_hashtable_merge(git_hashtable *self, git_hashtable *other)
return insert_nodes(self, other->nodes, other->key_count);
}
+
+/**
+ * Standard string
+ */
+uint32_t git_hash__strhash_cb(const void *key, int hash_id)
+{
+ static uint32_t hash_seeds[GIT_HASHTABLE_HASHES] = {
+ 2147483647,
+ 0x5d20bb23,
+ 0x7daaab3c
+ };
+
+ return git__hash(key, strlen((const char *)key), hash_seeds[hash_id]);
+}
diff --git a/src/hashtable.h b/src/hashtable.h
index f0ca3ebd2..485b17aa6 100644
--- a/src/hashtable.h
+++ b/src/hashtable.h
@@ -76,5 +76,12 @@ GIT_INLINE(int) git_hashtable_insert(git_hashtable *h, const void *key, void *va
_node->key = NULL; _node->value = NULL; _self->key_count--;\
}
+/*
+ * If you want a hashtable with standard string keys, you can
+ * just pass git_hash__strcmp_cb and git_hash__strhash_cb to
+ * git_hashtable_alloc.
+ */
+#define git_hash__strcmp_cb git__strcmp_cb
+extern uint32_t git_hash__strhash_cb(const void *key, int hash_id);
#endif
diff --git a/src/refs.c b/src/refs.c
index 4950fd595..2842adab1 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -31,17 +31,6 @@ struct packref {
static const int default_table_size = 32;
-static uint32_t reftable_hash(const void *key, int hash_id)
-{
- static uint32_t hash_seeds[GIT_HASHTABLE_HASHES] = {
- 2147483647,
- 0x5d20bb23,
- 0x7daaab3c
- };
-
- return git__hash(key, strlen((const char *)key), hash_seeds[hash_id]);
-}
-
static int reference_read(
git_fbuffer *file_content,
time_t *mtime,
@@ -445,9 +434,7 @@ static int packed_load(git_repository *repo)
/* First we make sure we have allocated the hash table */
if (ref_cache->packfile == NULL) {
ref_cache->packfile = git_hashtable_alloc(
- default_table_size,
- reftable_hash,
- (git_hash_keyeq_ptr)&git__strcmp_cb);
+ default_table_size, git_hash__strhash_cb, git_hash__strcmp_cb);
if (ref_cache->packfile == NULL) {
error = GIT_ENOMEM;
diff --git a/src/repository.c b/src/repository.c
index 67afa2ee2..a94ecce55 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -59,6 +59,7 @@ void git_repository_free(git_repository *repo)
git_cache_free(&repo->objects);
git_repository__refcache_free(&repo->references);
+ git_attr_cache_flush(repo);
git__free(repo->path_repository);
git__free(repo->workdir);
diff --git a/src/repository.h b/src/repository.h
index c3a9a5c60..82052158a 100644
--- a/src/repository.h
+++ b/src/repository.h
@@ -19,6 +19,7 @@
#include "refs.h"
#include "buffer.h"
#include "odb.h"
+#include "attr_file.h"
#define DOT_GIT ".git"
#define GIT_DIR DOT_GIT "/"
@@ -38,6 +39,7 @@ struct git_repository {
git_cache objects;
git_refcache references;
+ git_attr_cache attrcache;
char *path_repository;
char *workdir;
diff --git a/src/util.c b/src/util.c
index b3af7ffd8..1ca9d850c 100644
--- a/src/util.c
+++ b/src/util.c
@@ -348,22 +348,30 @@ uint32_t git__hash(const void *key, int len, uint32_t seed)
* Copyright (c) 1990 Regents of the University of California.
* All rights reserved.
*/
-void **git__bsearch(const void *key, void **base, size_t nmemb, int (*compar)(const void *, const void *))
+int git__bsearch(
+ void **array,
+ size_t array_len,
+ const void *key,
+ int (*compare)(const void *, const void *),
+ size_t *position)
{
- int lim, cmp;
- void **p;
-
- for (lim = nmemb; lim != 0; lim >>= 1) {
- p = base + (lim >> 1);
- cmp = (*compar)(key, *p);
- if (cmp > 0) { /* key > p: move right */
- base = p + 1;
- lim--;
- } else if (cmp == 0) {
- return (void **)p;
- } /* else move left */
- }
- return NULL;
+ int lim, cmp;
+ void **part, **base = array;
+
+ for (lim = array_len; lim != 0; lim >>= 1) {
+ part = base + (lim >> 1);
+ cmp = (*compare)(key, *part);
+ if (cmp == 0) {
+ *position = (part - array);
+ return GIT_SUCCESS;
+ } else if (cmp > 0) { /* key > p; take right partition */
+ base = part + 1;
+ lim--;
+ } /* else take left partition */
+ }
+
+ *position = (base - array);
+ return GIT_ENOTFOUND;
}
/**
diff --git a/src/util.h b/src/util.h
index 4b1104b7b..2367bb5f3 100644
--- a/src/util.h
+++ b/src/util.h
@@ -105,8 +105,13 @@ extern void git__strtolower(char *str);
extern int git__fnmatch(const char *pattern, const char *name, int flags);
extern void git__tsort(void **dst, size_t size, int (*cmp)(const void *, const void *));
-extern void **git__bsearch(const void *key, void **base, size_t nmemb,
- int (*compar)(const void *, const void *));
+
+extern int git__bsearch(
+ void **array,
+ size_t array_len,
+ const void *key,
+ int (*compare)(const void *, const void *),
+ size_t *position);
extern int git__strcmp_cb(const void *a, const void *b);
diff --git a/src/vector.c b/src/vector.c
index 123aae8e6..593d037d4 100644
--- a/src/vector.c
+++ b/src/vector.c
@@ -29,7 +29,12 @@ static int resize_vector(git_vector *v)
void git_vector_free(git_vector *v)
{
assert(v);
+
git__free(v->contents);
+ v->contents = NULL;
+
+ v->length = 0;
+ v->_alloc_size = 0;
}
int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp)
@@ -69,6 +74,45 @@ int git_vector_insert(git_vector *v, void *element)
return GIT_SUCCESS;
}
+int git_vector_insert_sorted(git_vector *v, void *element, int (*on_dup)(void **old, void *new))
+{
+ int error = GIT_SUCCESS;
+ size_t pos;
+
+ assert(v && v->_cmp);
+
+ if (!v->sorted)
+ git_vector_sort(v);
+
+ if (v->length >= v->_alloc_size) {
+ if (resize_vector(v) < 0)
+ return GIT_ENOMEM;
+ }
+
+ error = git__bsearch(v->contents, v->length, element, v->_cmp, &pos);
+
+ /* If we found the element and have a duplicate handler callback,
+ * invoke it. If it returns an error, then cancel insert, otherwise
+ * proceed with normal insert.
+ */
+ if (error == GIT_SUCCESS && on_dup != NULL) {
+ error = on_dup(&v->contents[pos], element);
+ if (error != GIT_SUCCESS)
+ return error;
+ }
+
+ /* shift elements to the right */
+ if (pos < v->length) {
+ memmove(v->contents + pos + 1, v->contents + pos,
+ (v->length - pos) * sizeof(void *));
+ }
+
+ v->contents[pos] = element;
+ v->length++;
+
+ return GIT_SUCCESS;
+}
+
void git_vector_sort(git_vector *v)
{
assert(v);
@@ -82,7 +126,7 @@ void git_vector_sort(git_vector *v)
int git_vector_bsearch2(git_vector *v, git_vector_cmp key_lookup, const void *key)
{
- void **find;
+ size_t pos;
assert(v && key && key_lookup);
@@ -92,9 +136,9 @@ int git_vector_bsearch2(git_vector *v, git_vector_cmp key_lookup, const void *ke
git_vector_sort(v);
- find = git__bsearch(key, v->contents, v->length, key_lookup);
- if (find != NULL)
- return (int)(find - v->contents);
+ if (git__bsearch(v->contents, v->length, key, key_lookup,
+ &pos) == GIT_SUCCESS)
+ return (int)pos;
return git__throw(GIT_ENOTFOUND, "Can't find element");
}
diff --git a/src/vector.h b/src/vector.h
index 08f5a501c..9ee3c9ed5 100644
--- a/src/vector.h
+++ b/src/vector.h
@@ -19,6 +19,8 @@ typedef struct git_vector {
int sorted;
} git_vector;
+#define GIT_VECTOR_INIT {0}
+
int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp);
void git_vector_free(git_vector *v);
void git_vector_clear(git_vector *v);
@@ -39,7 +41,13 @@ GIT_INLINE(void *) git_vector_get(git_vector *v, unsigned int position)
#define git_vector_foreach(v, iter, elem) \
for ((iter) = 0; (iter) < (v)->length && ((elem) = (v)->contents[(iter)], 1); (iter)++ )
+#define git_vector_rforeach(v, iter, elem) \
+ for ((iter) = (v)->length; (iter) > 0 && ((elem) = (v)->contents[(iter)-1], 1); (iter)-- )
+
int git_vector_insert(git_vector *v, void *element);
+int git_vector_insert_sorted(git_vector *v, void *element,
+ int (*on_dup)(void **old, void *new));
int git_vector_remove(git_vector *v, unsigned int idx);
void git_vector_uniq(git_vector *v);
+
#endif
diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c
index b41c78f92..b1b838eb7 100644
--- a/src/win32/utf-conv.c
+++ b/src/win32/utf-conv.c
@@ -57,6 +57,11 @@ wchar_t* gitwin_to_utf16(const char* str)
return ret;
}
+int gitwin_append_utf16(wchar_t *buffer, const char *str, size_t len)
+{
+ return MultiByteToWideChar(_active_codepage, 0, str, -1, buffer, len);
+}
+
char* gitwin_from_utf16(const wchar_t* str)
{
char* ret;
diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h
index da03e3385..bbb5c4f69 100644
--- a/src/win32/utf-conv.h
+++ b/src/win32/utf-conv.h
@@ -11,6 +11,7 @@
#define INCLUDE_git_utfconv_h__
wchar_t* gitwin_to_utf16(const char* str);
+int gitwin_append_utf16(wchar_t *buffer, const char *str, size_t len)
char* gitwin_from_utf16(const wchar_t* str);
#endif
diff --git a/tests-clay/attr/file.c b/tests-clay/attr/file.c
new file mode 100644
index 000000000..d9e2d5701
--- /dev/null
+++ b/tests-clay/attr/file.c
@@ -0,0 +1,223 @@
+#include "clay_libgit2.h"
+#include "attr_file.h"
+
+#define get_rule(X) ((git_attr_rule *)git_vector_get(&file->rules,(X)))
+#define get_assign(R,Y) ((git_attr_assignment *)git_vector_get(&(R)->assigns,(Y)))
+
+void test_attr_file__simple_read(void)
+{
+ git_attr_file *file = NULL;
+
+ cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr0"), &file));
+ cl_assert_strequal(cl_fixture("attr/attr0"), file->path);
+ cl_assert(file->rules.length == 1);
+
+ git_attr_rule *rule = get_rule(0);
+ cl_assert(rule != NULL);
+ cl_assert_strequal("*", rule->match.pattern);
+ cl_assert(rule->match.length == 1);
+ cl_assert(rule->match.flags == 0);
+
+ cl_assert(rule->assigns.length == 1);
+ git_attr_assignment *assign = get_assign(rule, 0);
+ cl_assert(assign != NULL);
+ cl_assert_strequal("binary", assign->name);
+ cl_assert(assign->value == GIT_ATTR_TRUE);
+ cl_assert(!assign->is_allocated);
+
+ git_attr_file__free(file);
+}
+
+void test_attr_file__match_variants(void)
+{
+ git_attr_file *file = NULL;
+ git_attr_rule *rule;
+ git_attr_assignment *assign;
+
+ cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr1"), &file));
+ cl_assert_strequal(cl_fixture("attr/attr1"), file->path);
+ cl_assert(file->rules.length == 10);
+
+ /* let's do a thorough check of this rule, then just verify
+ * the things that are unique for the later rules
+ */
+ rule = get_rule(0);
+ cl_assert(rule);
+ cl_assert_strequal("pat0", rule->match.pattern);
+ cl_assert(rule->match.length == strlen("pat0"));
+ cl_assert(rule->match.flags == 0);
+ cl_assert(rule->assigns.length == 1);
+ assign = get_assign(rule,0);
+ cl_assert_strequal("attr0", assign->name);
+ cl_assert(assign->name_hash == git_attr_file__name_hash(assign->name));
+ cl_assert(assign->value == GIT_ATTR_TRUE);
+ cl_assert(!assign->is_allocated);
+
+ rule = get_rule(1);
+ cl_assert_strequal("pat1", rule->match.pattern);
+ cl_assert(rule->match.length == strlen("pat1"));
+ cl_assert(rule->match.flags == GIT_ATTR_FNMATCH_NEGATIVE);
+
+ rule = get_rule(2);
+ cl_assert_strequal("pat2", rule->match.pattern);
+ cl_assert(rule->match.length == strlen("pat2"));
+ cl_assert(rule->match.flags == GIT_ATTR_FNMATCH_DIRECTORY);
+
+ rule = get_rule(3);
+ cl_assert_strequal("pat3dir/pat3file", rule->match.pattern);
+ cl_assert(rule->match.flags == GIT_ATTR_FNMATCH_FULLPATH);
+
+ rule = get_rule(4);
+ cl_assert_strequal("pat4.*", rule->match.pattern);
+ cl_assert(rule->match.flags == 0);
+
+ rule = get_rule(5);
+ cl_assert_strequal("*.pat5", rule->match.pattern);
+
+ rule = get_rule(7);
+ cl_assert_strequal("pat7[a-e]??[xyz]", rule->match.pattern);
+ cl_assert(rule->assigns.length == 1);
+ assign = get_assign(rule,0);
+ cl_assert_strequal("attr7", assign->name);
+ cl_assert(assign->value == GIT_ATTR_TRUE);
+
+ rule = get_rule(8);
+ cl_assert_strequal("pat8 with spaces", rule->match.pattern);
+ cl_assert(rule->match.length == strlen("pat8 with spaces"));
+ cl_assert(rule->match.flags == 0);
+
+ rule = get_rule(9);
+ cl_assert_strequal("pat9", rule->match.pattern);
+
+ git_attr_file__free(file);
+}
+
+static void check_one_assign(
+ git_attr_file *file,
+ int rule_idx,
+ int assign_idx,
+ const char *pattern,
+ const char *name,
+ const char *value,
+ int is_allocated)
+{
+ git_attr_rule *rule = get_rule(rule_idx);
+ git_attr_assignment *assign = get_assign(rule, assign_idx);
+
+ cl_assert_strequal(pattern, rule->match.pattern);
+ cl_assert(rule->assigns.length == 1);
+ cl_assert_strequal(name, assign->name);
+ cl_assert(assign->name_hash == git_attr_file__name_hash(assign->name));
+ cl_assert(assign->is_allocated == is_allocated);
+ if (is_allocated)
+ cl_assert_strequal(value, assign->value);
+ else
+ cl_assert(assign->value == value);
+}
+
+void test_attr_file__assign_variants(void)
+{
+ git_attr_file *file = NULL;
+ git_attr_rule *rule;
+ git_attr_assignment *assign;
+
+ cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr2"), &file));
+ cl_assert_strequal(cl_fixture("attr/attr2"), file->path);
+ cl_assert(file->rules.length == 11);
+
+ check_one_assign(file, 0, 0, "pat0", "simple", GIT_ATTR_TRUE, 0);
+ check_one_assign(file, 1, 0, "pat1", "neg", GIT_ATTR_FALSE, 0);
+ check_one_assign(file, 2, 0, "*", "notundef", GIT_ATTR_TRUE, 0);
+ check_one_assign(file, 3, 0, "pat2", "notundef", NULL, 0);
+ check_one_assign(file, 4, 0, "pat3", "assigned", "test-value", 1);
+ check_one_assign(file, 5, 0, "pat4", "rule-with-more-chars", "value-with-more-chars", 1);
+ check_one_assign(file, 6, 0, "pat5", "empty", GIT_ATTR_TRUE, 0);
+ check_one_assign(file, 7, 0, "pat6", "negempty", GIT_ATTR_FALSE, 0);
+
+ rule = get_rule(8);
+ cl_assert_strequal("pat7", rule->match.pattern);
+ cl_assert(rule->assigns.length == 5);
+ /* assignments will be sorted by hash value, so we have to do
+ * lookups by search instead of by position
+ */
+ assign = git_attr_rule__lookup_assignment(rule, "multiple");
+ cl_assert(assign);
+ cl_assert_strequal("multiple", assign->name);
+ cl_assert(assign->value == GIT_ATTR_TRUE);
+ assign = git_attr_rule__lookup_assignment(rule, "single");
+ cl_assert(assign);
+ cl_assert_strequal("single", assign->name);
+ cl_assert(assign->value == GIT_ATTR_FALSE);
+ assign = git_attr_rule__lookup_assignment(rule, "values");
+ cl_assert(assign);
+ cl_assert_strequal("values", assign->name);
+ cl_assert_strequal("1", assign->value);
+ assign = git_attr_rule__lookup_assignment(rule, "also");
+ cl_assert(assign);
+ cl_assert_strequal("also", assign->name);
+ cl_assert_strequal("a-really-long-value/*", assign->value);
+ assign = git_attr_rule__lookup_assignment(rule, "happy");
+ cl_assert(assign);
+ cl_assert_strequal("happy", assign->name);
+ cl_assert_strequal("yes!", assign->value);
+ assign = git_attr_rule__lookup_assignment(rule, "other");
+ cl_assert(!assign);
+
+ rule = get_rule(9);
+ cl_assert_strequal("pat8", rule->match.pattern);
+ cl_assert(rule->assigns.length == 2);
+ assign = git_attr_rule__lookup_assignment(rule, "again");
+ cl_assert(assign);
+ cl_assert_strequal("again", assign->name);
+ cl_assert(assign->value == GIT_ATTR_TRUE);
+ assign = git_attr_rule__lookup_assignment(rule, "another");
+ cl_assert(assign);
+ cl_assert_strequal("another", assign->name);
+ cl_assert_strequal("12321", assign->value);
+
+ check_one_assign(file, 10, 0, "pat9", "at-eof", GIT_ATTR_FALSE, 0);
+
+ git_attr_file__free(file);
+}
+
+void test_attr_file__check_attr_examples(void)
+{
+ git_attr_file *file = NULL;
+ git_attr_rule *rule;
+ git_attr_assignment *assign;
+
+ cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr3"), &file));
+ cl_assert_strequal(cl_fixture("attr/attr3"), file->path);
+ cl_assert(file->rules.length == 3);
+
+ rule = get_rule(0);
+ cl_assert_strequal("*.java", rule->match.pattern);
+ cl_assert(rule->assigns.length == 3);
+ assign = git_attr_rule__lookup_assignment(rule, "diff");
+ cl_assert_strequal("diff", assign->name);
+ cl_assert_strequal("java", assign->value);
+ assign = git_attr_rule__lookup_assignment(rule, "crlf");
+ cl_assert_strequal("crlf", assign->name);
+ cl_assert(GIT_ATTR_FALSE == assign->value);
+ assign = git_attr_rule__lookup_assignment(rule, "myAttr");
+ cl_assert_strequal("myAttr", assign->name);
+ cl_assert(GIT_ATTR_TRUE == assign->value);
+ assign = git_attr_rule__lookup_assignment(rule, "missing");
+ cl_assert(assign == NULL);
+
+ rule = get_rule(1);
+ cl_assert_strequal("NoMyAttr.java", rule->match.pattern);
+ cl_assert(rule->assigns.length == 1);
+ assign = get_assign(rule, 0);
+ cl_assert_strequal("myAttr", assign->name);
+ cl_assert(assign->value == NULL);
+
+ rule = get_rule(2);
+ cl_assert_strequal("README", rule->match.pattern);
+ cl_assert(rule->assigns.length == 1);
+ assign = get_assign(rule, 0);
+ cl_assert_strequal("caveat", assign->name);
+ cl_assert_strequal("unspecified", assign->value);
+
+ git_attr_file__free(file);
+}
diff --git a/tests-clay/attr/lookup.c b/tests-clay/attr/lookup.c
new file mode 100644
index 000000000..fcade5225
--- /dev/null
+++ b/tests-clay/attr/lookup.c
@@ -0,0 +1,257 @@
+#include "clay_libgit2.h"
+#include "attr_file.h"
+
+void test_attr_lookup__simple(void)
+{
+ git_attr_file *file = NULL;
+ git_attr_path path;
+ const char *value = NULL;
+
+ cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr0"), &file));
+ cl_assert_strequal(cl_fixture("attr/attr0"), file->path);
+ cl_assert(file->rules.length == 1);
+
+ cl_git_pass(git_attr_path__init(&path, "test"));
+ cl_assert_strequal("test", path.path);
+ cl_assert_strequal("test", path.basename);
+ cl_assert(!path.is_dir);
+
+ cl_git_pass(git_attr_file__lookup_one(file,&path,"binary",&value));
+ cl_assert(value == GIT_ATTR_TRUE);
+
+ cl_git_pass(git_attr_file__lookup_one(file,&path,"missing",&value));
+ cl_assert(!value);
+
+ git_attr_file__free(file);
+}
+
+typedef struct {
+ const char *path;
+ const char *attr;
+ const char *expected;
+ int use_strcmp;
+ int force_dir;
+} test_case;
+
+static void run_test_cases(git_attr_file *file, test_case *cases)
+{
+ git_attr_path path;
+ const char *value = NULL;
+ test_case *c;
+ int error;
+
+ for (c = cases; c->path != NULL; c++) {
+ cl_git_pass(git_attr_path__init(&path, c->path));
+
+ if (c->force_dir)
+ path.is_dir = 1;
+
+ error = git_attr_file__lookup_one(file,&path,c->attr,&value);
+ if (error != GIT_SUCCESS)
+ fprintf(stderr, "failure with %s %s %s\n", c->path, c->attr, c->expected);
+ cl_git_pass(error);
+
+ if (c->use_strcmp)
+ cl_assert_strequal(c->expected, value);
+ else
+ cl_assert(c->expected == value);
+ }
+}
+
+void test_attr_lookup__match_variants(void)
+{
+ git_attr_file *file = NULL;
+ git_attr_path path;
+ test_case cases[] = {
+ /* pat0 -> simple match */
+ { "pat0", "attr0", GIT_ATTR_TRUE, 0, 0 },
+ { "/testing/for/pat0", "attr0", GIT_ATTR_TRUE, 0, 0 },
+ { "relative/to/pat0", "attr0", GIT_ATTR_TRUE, 0, 0 },
+ { "this-contains-pat0-inside", "attr0", NULL, 0, 0 },
+ { "this-aint-right", "attr0", NULL, 0, 0 },
+ { "/this/pat0/dont/match", "attr0", NULL, 0, 0 },
+ /* negative match */
+ { "pat0", "attr1", GIT_ATTR_TRUE, 0, 0 },
+ { "pat1", "attr1", NULL, 0, 0 },
+ { "/testing/for/pat1", "attr1", NULL, 0, 0 },
+ { "/testing/for/pat0", "attr1", GIT_ATTR_TRUE, 0, 0 },
+ { "/testing/for/pat1/inside", "attr1", GIT_ATTR_TRUE, 0, 0 },
+ { "misc", "attr1", GIT_ATTR_TRUE, 0, 0 },
+ /* dir match */
+ { "pat2", "attr2", NULL, 0, 0 },
+ { "pat2", "attr2", GIT_ATTR_TRUE, 0, 1 },
+ { "/testing/for/pat2", "attr2", NULL, 0, 0 },
+ { "/testing/for/pat2", "attr2", GIT_ATTR_TRUE, 0, 1 },
+ { "/not/pat2/yousee", "attr2", NULL, 0, 0 },
+ { "/not/pat2/yousee", "attr2", NULL, 0, 1 },
+ /* path match */
+ { "pat3file", "attr3", NULL, 0, 0 },
+ { "/pat3dir/pat3file", "attr3", NULL, 0, 0 },
+ { "pat3dir/pat3file", "attr3", GIT_ATTR_TRUE, 0, 0 },
+ /* pattern* match */
+ { "pat4.txt", "attr4", GIT_ATTR_TRUE, 0, 0 },
+ { "/fun/fun/fun/pat4.c", "attr4", GIT_ATTR_TRUE, 0, 0 },
+ { "pat4.", "attr4", GIT_ATTR_TRUE, 0, 0 },
+ { "pat4", "attr4", NULL, 0, 0 },
+ { "/fun/fun/fun/pat4.dir", "attr4", GIT_ATTR_TRUE, 0, 1 },
+ /* *pattern match */
+ { "foo.pat5", "attr5", GIT_ATTR_TRUE, 0, 0 },
+ { "foo.pat5", "attr5", GIT_ATTR_TRUE, 0, 1 },
+ { "/this/is/ok.pat5", "attr5", GIT_ATTR_TRUE, 0, 0 },
+ { "/this/is/bad.pat5/yousee.txt", "attr5", NULL, 0, 0 },
+ { "foo.pat5", "attr100", NULL, 0, 0 },
+ /* glob match with slashes */
+ { "foo.pat6", "attr6", NULL, 0, 0 },
+ { "pat6/pat6/foobar.pat6", "attr6", GIT_ATTR_TRUE, 0, 0 },
+ { "pat6/pat6/.pat6", "attr6", GIT_ATTR_TRUE, 0, 0 },
+ { "pat6/pat6/extra/foobar.pat6", "attr6", NULL, 0, 0 },
+ { "/prefix/pat6/pat6/foobar.pat6", "attr6", NULL, 0, 0 },
+ { "/pat6/pat6/foobar.pat6", "attr6", NULL, 0, 0 },
+ /* complex pattern */
+ { "pat7a12z", "attr7", GIT_ATTR_TRUE, 0, 0 },
+ { "pat7e__x", "attr7", GIT_ATTR_TRUE, 0, 0 },
+ { "pat7b/1y", "attr7", NULL, 0, 0 }, /* ? does not match / */
+ { "pat7e_x", "attr7", NULL, 0, 0 },
+ { "pat7aaaa", "attr7", NULL, 0, 0 },
+ { "pat7zzzz", "attr7", NULL, 0, 0 },
+ { "/this/can/be/anything/pat7a12z", "attr7", GIT_ATTR_TRUE, 0, 0 },
+ { "but/it/still/must/match/pat7aaaa", "attr7", NULL, 0, 0 },
+ { "pat7aaay.fail", "attr7", NULL, 0, 0 },
+ /* pattern with spaces */
+ { "pat8 with spaces", "attr8", GIT_ATTR_TRUE, 0, 0 },
+ { "/gotta love/pat8 with spaces", "attr8", GIT_ATTR_TRUE, 0, 0 },
+ { "failing pat8 with spaces", "attr8", NULL, 0, 0 },
+ { "spaces", "attr8", NULL, 0, 0 },
+ /* pattern at eof */
+ { "pat9", "attr9", GIT_ATTR_TRUE, 0, 0 },
+ { "/eof/pat9", "attr9", GIT_ATTR_TRUE, 0, 0 },
+ { "pat", "attr9", NULL, 0, 0 },
+ { "at9", "attr9", NULL, 0, 0 },
+ { "pat9.fail", "attr9", NULL, 0, 0 },
+ /* sentinel at end */
+ { NULL, NULL, NULL, 0, 0 }
+ };
+
+ cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr1"), &file));
+ cl_assert_strequal(cl_fixture("attr/attr1"), file->path);
+ cl_assert(file->rules.length == 10);
+
+ cl_git_pass(git_attr_path__init(&path, "/testing/for/pat0"));
+ cl_assert_strequal("pat0", path.basename);
+
+ run_test_cases(file, cases);
+
+ git_attr_file__free(file);
+}
+
+void test_attr_lookup__assign_variants(void)
+{
+ git_attr_file *file = NULL;
+ test_case cases[] = {
+ /* pat0 -> simple assign */
+ { "pat0", "simple", GIT_ATTR_TRUE, 0, 0 },
+ { "/testing/pat0", "simple", GIT_ATTR_TRUE, 0, 0 },
+ { "pat0", "fail", NULL, 0, 0 },
+ { "/testing/pat0", "fail", NULL, 0, 0 },
+ /* negative assign */
+ { "pat1", "neg", GIT_ATTR_FALSE, 0, 0 },
+ { "/testing/pat1", "neg", GIT_ATTR_FALSE, 0, 0 },
+ { "pat1", "fail", NULL, 0, 0 },
+ { "/testing/pat1", "fail", NULL, 0, 0 },
+ /* forced undef */
+ { "pat1", "notundef", GIT_ATTR_TRUE, 0, 0 },
+ { "pat2", "notundef", NULL, 0, 0 },
+ { "/lead/in/pat1", "notundef", GIT_ATTR_TRUE, 0, 0 },
+ { "/lead/in/pat2", "notundef", NULL, 0, 0 },
+ /* assign value */
+ { "pat3", "assigned", "test-value", 1, 0 },
+ { "pat3", "notassigned", NULL, 0, 0 },
+ /* assign value */
+ { "pat4", "rule-with-more-chars", "value-with-more-chars", 1, 0 },
+ { "pat4", "notassigned-rule-with-more-chars", NULL, 0, 0 },
+ /* empty assignments */
+ { "pat5", "empty", GIT_ATTR_TRUE, 0, 0 },
+ { "pat6", "negempty", GIT_ATTR_FALSE, 0, 0 },
+ /* multiple assignment */
+ { "pat7", "multiple", GIT_ATTR_TRUE, 0, 0 },
+ { "pat7", "single", GIT_ATTR_FALSE, 0, 0 },
+ { "pat7", "values", "1", 1, 0 },
+ { "pat7", "also", "a-really-long-value/*", 1, 0 },
+ { "pat7", "happy", "yes!", 1, 0 },
+ { "pat8", "again", GIT_ATTR_TRUE, 0, 0 },
+ { "pat8", "another", "12321", 1, 0 },
+ /* bad assignment */
+ { "patbad0", "simple", NULL, 0, 0 },
+ { "patbad0", "notundef", GIT_ATTR_TRUE, 0, 0 },
+ { "patbad1", "simple", NULL, 0, 0 },
+ /* eof assignment */
+ { "pat9", "at-eof", GIT_ATTR_FALSE, 0, 0 },
+ /* sentinel at end */
+ { NULL, NULL, NULL, 0, 0 }
+ };
+
+ cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr2"), &file));
+ cl_assert(file->rules.length == 11);
+
+ run_test_cases(file, cases);
+
+ git_attr_file__free(file);
+}
+
+void test_attr_lookup__check_attr_examples(void)
+{
+ git_attr_file *file = NULL;
+ test_case cases[] = {
+ { "foo.java", "diff", "java", 1, 0 },
+ { "foo.java", "crlf", GIT_ATTR_FALSE, 0, 0 },
+ { "foo.java", "myAttr", GIT_ATTR_TRUE, 0, 0 },
+ { "foo.java", "other", NULL, 0, 0 },
+ { "/prefix/dir/foo.java", "diff", "java", 1, 0 },
+ { "/prefix/dir/foo.java", "crlf", GIT_ATTR_FALSE, 0, 0 },
+ { "/prefix/dir/foo.java", "myAttr", GIT_ATTR_TRUE, 0, 0 },
+ { "/prefix/dir/foo.java", "other", NULL, 0, 0 },
+ { "NoMyAttr.java", "crlf", GIT_ATTR_FALSE, 0, 0 },
+ { "NoMyAttr.java", "myAttr", NULL, 0, 0 },
+ { "NoMyAttr.java", "other", NULL, 0, 0 },
+ { "/prefix/dir/NoMyAttr.java", "crlf", GIT_ATTR_FALSE, 0, 0 },
+ { "/prefix/dir/NoMyAttr.java", "myAttr", NULL, 0, 0 },
+ { "/prefix/dir/NoMyAttr.java", "other", NULL, 0, 0 },
+ { "README", "caveat", "unspecified", 1, 0 },
+ { "/specific/path/README", "caveat", "unspecified", 1, 0 },
+ { "README", "missing", NULL, 0, 0 },
+ { "/specific/path/README", "missing", NULL, 0, 0 },
+ /* sentinel at end */
+ { NULL, NULL, NULL, 0, 0 }
+ };
+
+ cl_git_pass(git_attr_file__from_file(NULL, cl_fixture("attr/attr3"), &file));
+ cl_assert(file->rules.length == 3);
+
+ run_test_cases(file, cases);
+
+ git_attr_file__free(file);
+}
+
+void test_attr_lookup__from_buffer(void)
+{
+ git_attr_file *file = NULL;
+ test_case cases[] = {
+ { "abc", "foo", GIT_ATTR_TRUE, 0, 0 },
+ { "abc", "bar", GIT_ATTR_TRUE, 0, 0 },
+ { "abc", "baz", GIT_ATTR_TRUE, 0, 0 },
+ { "aaa", "foo", GIT_ATTR_TRUE, 0, 0 },
+ { "aaa", "bar", NULL, 0, 0 },
+ { "aaa", "baz", GIT_ATTR_TRUE, 0, 0 },
+ { "qqq", "foo", NULL, 0, 0 },
+ { "qqq", "bar", NULL, 0, 0 },
+ { "qqq", "baz", GIT_ATTR_TRUE, 0, 0 },
+ { NULL, NULL, NULL, 0, 0 }
+ };
+
+ cl_git_pass(git_attr_file__from_buffer(NULL, "a* foo\nabc bar\n* baz", &file));
+ cl_assert(file->rules.length == 3);
+
+ run_test_cases(file, cases);
+
+ git_attr_file__free(file);
+}
diff --git a/tests-clay/attr/repo.c b/tests-clay/attr/repo.c
new file mode 100644
index 000000000..f87e7bf55
--- /dev/null
+++ b/tests-clay/attr/repo.c
@@ -0,0 +1,236 @@
+#include "clay_libgit2.h"
+#include "fileops.h"
+#include "git2/attr.h"
+
+static git_repository *g_repo = NULL;
+
+void test_attr_repo__initialize(void)
+{
+ /* Before each test, instantiate the attr repo from the fixtures and
+ * rename the .gitted to .git so it is a repo with a working dir.
+ * Also rename gitattributes to .gitattributes, because it contains
+ * macro definitions which are only allowed in the root.
+ */
+ cl_fixture_sandbox("attr");
+ cl_git_pass(p_rename("attr/.gitted", "attr/.git"));
+ cl_git_pass(p_rename("attr/gitattributes", "attr/.gitattributes"));
+ cl_git_pass(git_repository_open(&g_repo, "attr/.git"));
+}
+
+void test_attr_repo__cleanup(void)
+{
+ git_repository_free(g_repo);
+ g_repo = NULL;
+ cl_fixture_cleanup("attr");
+}
+
+void test_attr_repo__get_one(void)
+{
+ const char *value;
+ struct {
+ const char *file;
+ const char *attr;
+ const char *expected;
+ } test_cases[] = {
+ { "root_test1", "repoattr", GIT_ATTR_TRUE },
+ { "root_test1", "rootattr", GIT_ATTR_TRUE },
+ { "root_test1", "missingattr", NULL },
+ { "root_test1", "subattr", NULL },
+ { "root_test1", "negattr", NULL },
+ { "root_test2", "repoattr", GIT_ATTR_TRUE },
+ { "root_test2", "rootattr", GIT_ATTR_FALSE },
+ { "root_test2", "missingattr", NULL },
+ { "root_test2", "multiattr", GIT_ATTR_FALSE },
+ { "root_test3", "repoattr", GIT_ATTR_TRUE },
+ { "root_test3", "rootattr", NULL },
+ { "root_test3", "multiattr", "3" },
+ { "root_test3", "multi2", NULL },
+ { "subdir/subdir_test1", "repoattr", GIT_ATTR_TRUE },
+ { "subdir/subdir_test1", "rootattr", GIT_ATTR_TRUE },
+ { "subdir/subdir_test1", "missingattr", NULL },
+ { "subdir/subdir_test1", "subattr", "yes" },
+ { "subdir/subdir_test1", "negattr", GIT_ATTR_FALSE },
+ { "subdir/subdir_test1", "another", NULL },
+ { "subdir/subdir_test2.txt", "repoattr", GIT_ATTR_TRUE },
+ { "subdir/subdir_test2.txt", "rootattr", GIT_ATTR_TRUE },
+ { "subdir/subdir_test2.txt", "missingattr", NULL },
+ { "subdir/subdir_test2.txt", "subattr", "yes" },
+ { "subdir/subdir_test2.txt", "negattr", GIT_ATTR_FALSE },
+ { "subdir/subdir_test2.txt", "another", "one" },
+ { NULL, NULL, NULL }
+ }, *scan;
+
+ for (scan = test_cases; scan->file != NULL; scan++) {
+ git_buf b = GIT_BUF_INIT;
+
+ git_buf_printf(&b, "%s:%s == expect %s",
+ scan->file, scan->attr, scan->expected);
+
+ cl_must_pass_(
+ git_attr_get(g_repo, scan->file, scan->attr, &value) == GIT_SUCCESS,
+ b.ptr);
+
+ git_buf_printf(&b, ", got %s", value);
+
+ if (scan->expected == NULL ||
+ scan->expected == GIT_ATTR_TRUE ||
+ scan->expected == GIT_ATTR_FALSE)
+ {
+ cl_assert_(scan->expected == value, b.ptr);
+ } else {
+ cl_assert_strequal(scan->expected, value);
+ }
+
+ git_buf_free(&b);
+ }
+}
+
+void test_attr_repo__get_many(void)
+{
+ const char *names[4] = { "repoattr", "rootattr", "missingattr", "subattr" };
+ const char *values[4];
+
+ cl_git_pass(git_attr_get_many(g_repo, "root_test1", 4, names, values));
+
+ cl_assert(values[0] == GIT_ATTR_TRUE);
+ cl_assert(values[1] == GIT_ATTR_TRUE);
+ cl_assert(values[2] == NULL);
+ cl_assert(values[3] == NULL);
+
+ cl_git_pass(git_attr_get_many(g_repo, "root_test2", 4, names, values));
+
+ cl_assert(values[0] == GIT_ATTR_TRUE);
+ cl_assert(values[1] == GIT_ATTR_FALSE);
+ cl_assert(values[2] == NULL);
+ cl_assert(values[3] == NULL);
+
+ cl_git_pass(git_attr_get_many(g_repo, "subdir/subdir_test1", 4, names, values));
+
+ cl_assert(values[0] == GIT_ATTR_TRUE);
+ cl_assert(values[1] == GIT_ATTR_TRUE);
+ cl_assert(values[2] == NULL);
+ cl_assert_strequal("yes", values[3]);
+
+}
+
+static int count_attrs(
+ const char *GIT_UNUSED(name),
+ const char *GIT_UNUSED(value),
+ void *payload)
+{
+ GIT_UNUSED_ARG(name);
+ GIT_UNUSED_ARG(value);
+
+ *((int *)payload) += 1;
+
+ return GIT_SUCCESS;
+}
+
+void test_attr_repo__foreach(void)
+{
+ int count;
+
+ count = 0;
+ cl_git_pass(git_attr_foreach(g_repo, "root_test1", &count_attrs, &count));
+ cl_assert(count == 2);
+
+ count = 0;
+ cl_git_pass(git_attr_foreach(g_repo, "subdir/subdir_test1",
+ &count_attrs, &count));
+ cl_assert(count == 4); /* repoattr, rootattr, subattr, negattr */
+
+ count = 0;
+ cl_git_pass(git_attr_foreach(g_repo, "subdir/subdir_test2.txt",
+ &count_attrs, &count));
+ cl_assert(count == 5); /* repoattr, rootattr, subattr, negattr, another */
+}
+
+void test_attr_repo__manpage_example(void)
+{
+ const char *value;
+
+ cl_git_pass(git_attr_get(g_repo, "subdir/abc", "foo", &value));
+ cl_assert(value == GIT_ATTR_TRUE);
+
+ cl_git_pass(git_attr_get(g_repo, "subdir/abc", "bar", &value));
+ cl_assert(value == NULL);
+
+ cl_git_pass(git_attr_get(g_repo, "subdir/abc", "baz", &value));
+ cl_assert(value == GIT_ATTR_FALSE);
+
+ cl_git_pass(git_attr_get(g_repo, "subdir/abc", "merge", &value));
+ cl_assert_strequal("filfre", value);
+
+ cl_git_pass(git_attr_get(g_repo, "subdir/abc", "frotz", &value));
+ cl_assert(value == NULL);
+}
+
+void test_attr_repo__macros(void)
+{
+ const char *names[5] = { "rootattr", "binary", "diff", "crlf", "frotz" };
+ const char *names2[5] = { "mymacro", "positive", "negative", "rootattr", "another" };
+ const char *names3[3] = { "macro2", "multi2", "multi3" };
+ const char *values[5];
+
+ cl_git_pass(git_attr_get_many(g_repo, "binfile", 5, names, values));
+
+ cl_assert(values[0] == GIT_ATTR_TRUE);
+ cl_assert(values[1] == GIT_ATTR_TRUE);
+ cl_assert(values[2] == GIT_ATTR_FALSE);
+ cl_assert(values[3] == GIT_ATTR_FALSE);
+ cl_assert(values[4] == NULL);
+
+ cl_git_pass(git_attr_get_many(g_repo, "macro_test", 5, names2, values));
+
+ cl_assert(values[0] == GIT_ATTR_TRUE);
+ cl_assert(values[1] == GIT_ATTR_TRUE);
+ cl_assert(values[2] == GIT_ATTR_FALSE);
+ cl_assert(values[3] == NULL);
+ cl_assert_strequal("77", values[4]);
+
+ cl_git_pass(git_attr_get_many(g_repo, "macro_test", 3, names3, values));
+
+ cl_assert(values[0] == GIT_ATTR_TRUE);
+ cl_assert(values[1] == GIT_ATTR_FALSE);
+ cl_assert_strequal("answer", values[2]);
+}
+
+void test_attr_repo__bad_macros(void)
+{
+ const char *names[6] = { "rootattr", "positive", "negative",
+ "firstmacro", "secondmacro", "thirdmacro" };
+ const char *values[6];
+
+ cl_git_pass(git_attr_get_many(g_repo, "macro_bad", 6, names, values));
+
+ /* these three just confirm that the "mymacro" rule ran */
+ cl_assert(values[0] == NULL);
+ cl_assert(values[1] == GIT_ATTR_TRUE);
+ cl_assert(values[2] == GIT_ATTR_FALSE);
+
+ /* file contains:
+ * # let's try some malicious macro defs
+ * [attr]firstmacro -thirdmacro -secondmacro
+ * [attr]secondmacro firstmacro -firstmacro
+ * [attr]thirdmacro secondmacro=hahaha -firstmacro
+ * macro_bad firstmacro secondmacro thirdmacro
+ *
+ * firstmacro assignment list ends up with:
+ * -thirdmacro -secondmacro
+ * secondmacro assignment list expands "firstmacro" and ends up with:
+ * -thirdmacro -secondmacro -firstmacro
+ * thirdmacro assignment don't expand so list ends up with:
+ * secondmacro="hahaha"
+ *
+ * macro_bad assignment list ends up with:
+ * -thirdmacro -secondmacro firstmacro &&
+ * -thirdmacro -secondmacro -firstmacro secondmacro &&
+ * secondmacro="hahaha" thirdmacro
+ *
+ * so summary results should be:
+ * -firstmacro secondmacro="hahaha" thirdmacro
+ */
+ cl_assert(values[3] == GIT_ATTR_FALSE);
+ cl_assert_strequal("hahaha", values[4]);
+ cl_assert(values[5] == GIT_ATTR_TRUE);
+}
diff --git a/tests-clay/clay.h b/tests-clay/clay.h
index c9fe4c166..98c306215 100644
--- a/tests-clay/clay.h
+++ b/tests-clay/clay.h
@@ -59,6 +59,23 @@ void cl_fixture_cleanup(const char *fixture_name);
*/
extern void clay_on_init(void);
extern void clay_on_shutdown(void);
+extern void test_attr_file__assign_variants(void);
+extern void test_attr_file__check_attr_examples(void);
+extern void test_attr_file__match_variants(void);
+extern void test_attr_file__simple_read(void);
+extern void test_attr_lookup__assign_variants(void);
+extern void test_attr_lookup__check_attr_examples(void);
+extern void test_attr_lookup__from_buffer(void);
+extern void test_attr_lookup__match_variants(void);
+extern void test_attr_lookup__simple(void);
+extern void test_attr_repo__bad_macros(void);
+extern void test_attr_repo__cleanup(void);
+extern void test_attr_repo__foreach(void);
+extern void test_attr_repo__get_many(void);
+extern void test_attr_repo__get_one(void);
+extern void test_attr_repo__initialize(void);
+extern void test_attr_repo__macros(void);
+extern void test_attr_repo__manpage_example(void);
extern void test_buf_basic__printf(void);
extern void test_buf_basic__resize(void);
extern void test_config_add__cleanup(void);
@@ -125,6 +142,9 @@ extern void test_core_strtol__int64(void);
extern void test_core_vector__0(void);
extern void test_core_vector__1(void);
extern void test_core_vector__2(void);
+extern void test_core_vector__3(void);
+extern void test_core_vector__4(void);
+extern void test_core_vector__5(void);
extern void test_index_rename__single_file(void);
extern void test_network_remotelocal__cleanup(void);
extern void test_network_remotelocal__initialize(void);
diff --git a/tests-clay/clay_main.c b/tests-clay/clay_main.c
index 318e096b6..d9ef970c5 100644
--- a/tests-clay/clay_main.c
+++ b/tests-clay/clay_main.c
@@ -108,6 +108,27 @@ static int clay_sandbox(void);
#define clay_on_suite() /* nop */
/* Autogenerated test data by clay */
+static const struct clay_func _clay_cb_attr_file[] = {
+ {"assign_variants", &test_attr_file__assign_variants},
+ {"check_attr_examples", &test_attr_file__check_attr_examples},
+ {"match_variants", &test_attr_file__match_variants},
+ {"simple_read", &test_attr_file__simple_read}
+};
+static const struct clay_func _clay_cb_attr_lookup[] = {
+ {"assign_variants", &test_attr_lookup__assign_variants},
+ {"check_attr_examples", &test_attr_lookup__check_attr_examples},
+ {"from_buffer", &test_attr_lookup__from_buffer},
+ {"match_variants", &test_attr_lookup__match_variants},
+ {"simple", &test_attr_lookup__simple}
+};
+static const struct clay_func _clay_cb_attr_repo[] = {
+ {"bad_macros", &test_attr_repo__bad_macros},
+ {"foreach", &test_attr_repo__foreach},
+ {"get_many", &test_attr_repo__get_many},
+ {"get_one", &test_attr_repo__get_one},
+ {"macros", &test_attr_repo__macros},
+ {"manpage_example", &test_attr_repo__manpage_example}
+};
static const struct clay_func _clay_cb_buf_basic[] = {
{"printf", &test_buf_basic__printf},
{"resize", &test_buf_basic__resize}
@@ -194,7 +215,10 @@ static const struct clay_func _clay_cb_core_strtol[] = {
static const struct clay_func _clay_cb_core_vector[] = {
{"0", &test_core_vector__0},
{"1", &test_core_vector__1},
- {"2", &test_core_vector__2}
+ {"2", &test_core_vector__2},
+ {"3", &test_core_vector__3},
+ {"4", &test_core_vector__4},
+ {"5", &test_core_vector__5}
};
static const struct clay_func _clay_cb_index_rename[] = {
{"single_file", &test_index_rename__single_file}
@@ -309,6 +333,24 @@ static const struct clay_func _clay_cb_status_worktree[] = {
static const struct clay_suite _clay_suites[] = {
{
+ "attr::file",
+ {NULL, NULL},
+ {NULL, NULL},
+ _clay_cb_attr_file, 4
+ },
+ {
+ "attr::lookup",
+ {NULL, NULL},
+ {NULL, NULL},
+ _clay_cb_attr_lookup, 5
+ },
+ {
+ "attr::repo",
+ {"initialize", &test_attr_repo__initialize},
+ {"cleanup", &test_attr_repo__cleanup},
+ _clay_cb_attr_repo, 6
+ },
+ {
"buf::basic",
{NULL, NULL},
{NULL, NULL},
@@ -396,7 +438,7 @@ static const struct clay_suite _clay_suites[] = {
"core::vector",
{NULL, NULL},
{NULL, NULL},
- _clay_cb_core_vector, 3
+ _clay_cb_core_vector, 6
},
{
"index::rename",
@@ -538,8 +580,8 @@ static const struct clay_suite _clay_suites[] = {
}
};
-static size_t _clay_suite_count = 38;
-static size_t _clay_callback_count = 122;
+static size_t _clay_suite_count = 41;
+static size_t _clay_callback_count = 140;
/* Core test functions */
static void
diff --git a/tests-clay/core/vector.c b/tests-clay/core/vector.c
index b8a853c60..fdcfb3a77 100644
--- a/tests-clay/core/vector.c
+++ b/tests-clay/core/vector.c
@@ -64,3 +64,128 @@ void test_core_vector__2(void)
}
+static int compare_them(const void *a, const void *b)
+{
+ return (int)((long)a - (long)b);
+}
+
+/* insert_sorted */
+void test_core_vector__3(void)
+{
+ git_vector x;
+ long i;
+ git_vector_init(&x, 1, &compare_them);
+
+ for (i = 0; i < 10; i += 2) {
+ git_vector_insert_sorted(&x, (void*)(i + 1), NULL);
+ }
+
+ for (i = 9; i > 0; i -= 2) {
+ git_vector_insert_sorted(&x, (void*)(i + 1), NULL);
+ }
+
+ cl_assert(x.length == 10);
+ for (i = 0; i < 10; ++i) {
+ cl_assert(git_vector_get(&x, i) == (void*)(i + 1));
+ }
+
+ git_vector_free(&x);
+}
+
+/* insert_sorted with duplicates */
+void test_core_vector__4(void)
+{
+ git_vector x;
+ long i;
+ git_vector_init(&x, 1, &compare_them);
+
+ for (i = 0; i < 10; i += 2) {
+ git_vector_insert_sorted(&x, (void*)(i + 1), NULL);
+ }
+
+ for (i = 9; i > 0; i -= 2) {
+ git_vector_insert_sorted(&x, (void*)(i + 1), NULL);
+ }
+
+ for (i = 0; i < 10; i += 2) {
+ git_vector_insert_sorted(&x, (void*)(i + 1), NULL);
+ }
+
+ for (i = 9; i > 0; i -= 2) {
+ git_vector_insert_sorted(&x, (void*)(i + 1), NULL);
+ }
+
+ cl_assert(x.length == 20);
+ for (i = 0; i < 20; ++i) {
+ cl_assert(git_vector_get(&x, i) == (void*)(i / 2 + 1));
+ }
+
+ git_vector_free(&x);
+}
+
+typedef struct {
+ int content;
+ int count;
+} my_struct;
+
+static int _struct_count = 0;
+
+static int compare_structs(const void *a, const void *b)
+{
+ return ((const my_struct *)a)->content -
+ ((const my_struct *)b)->content;
+}
+
+static int merge_structs(void **old_raw, void *new)
+{
+ my_struct *old = *(my_struct **)old_raw;
+ cl_assert(((my_struct *)old)->content == ((my_struct *)new)->content);
+ ((my_struct *)old)->count += 1;
+ git__free(new);
+ _struct_count--;
+ return GIT_EEXISTS;
+}
+
+static my_struct *alloc_struct(int value)
+{
+ my_struct *st = git__malloc(sizeof(my_struct));
+ st->content = value;
+ st->count = 0;
+ _struct_count++;
+ return st;
+}
+
+/* insert_sorted with duplicates and special handling */
+void test_core_vector__5(void)
+{
+ git_vector x;
+ int i;
+
+ git_vector_init(&x, 1, &compare_structs);
+
+ for (i = 0; i < 10; i += 2)
+ git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs);
+
+ for (i = 9; i > 0; i -= 2)
+ git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs);
+
+ cl_assert(x.length == 10);
+ cl_assert(_struct_count == 10);
+
+ for (i = 0; i < 10; i += 2)
+ git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs);
+
+ for (i = 9; i > 0; i -= 2)
+ git_vector_insert_sorted(&x, alloc_struct(i), &merge_structs);
+
+ cl_assert(x.length == 10);
+ cl_assert(_struct_count == 10);
+
+ for (i = 0; i < 10; ++i) {
+ cl_assert(((my_struct *)git_vector_get(&x, i))->content == i);
+ git__free(git_vector_get(&x, i));
+ _struct_count--;
+ }
+
+ git_vector_free(&x);
+}
diff --git a/tests/resources/attr/.gitted/HEAD b/tests/resources/attr/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests/resources/attr/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/attr/.gitted/config b/tests/resources/attr/.gitted/config
new file mode 100644
index 000000000..af107929f
--- /dev/null
+++ b/tests/resources/attr/.gitted/config
@@ -0,0 +1,6 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ ignorecase = true
diff --git a/tests/resources/attr/.gitted/description b/tests/resources/attr/.gitted/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests/resources/attr/.gitted/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests/resources/attr/.gitted/index b/tests/resources/attr/.gitted/index
new file mode 100644
index 000000000..c52747e0b
--- /dev/null
+++ b/tests/resources/attr/.gitted/index
Binary files differ
diff --git a/tests/resources/attr/.gitted/info/attributes b/tests/resources/attr/.gitted/info/attributes
new file mode 100644
index 000000000..2e9643a53
--- /dev/null
+++ b/tests/resources/attr/.gitted/info/attributes
@@ -0,0 +1,2 @@
+* repoattr
+a* foo !bar -baz
diff --git a/tests/resources/attr/.gitted/info/exclude b/tests/resources/attr/.gitted/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests/resources/attr/.gitted/info/exclude
@@ -0,0 +1,6 @@
+# git ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
diff --git a/tests/resources/attr/.gitted/logs/HEAD b/tests/resources/attr/.gitted/logs/HEAD
new file mode 100644
index 000000000..f518a465a
--- /dev/null
+++ b/tests/resources/attr/.gitted/logs/HEAD
@@ -0,0 +1,3 @@
+0000000000000000000000000000000000000000 6bab5c79cd5140d0f800917f550eb2a3dc32b0da Russell Belfer <arrbee@arrbee.com> 1324416995 -0800 commit (initial): initial test data
+6bab5c79cd5140d0f800917f550eb2a3dc32b0da 605812ab7fe421fdd325a935d35cb06a9234a7d7 Russell Belfer <arrbee@arrbee.com> 1325143098 -0800 commit: latest test updates
+605812ab7fe421fdd325a935d35cb06a9234a7d7 a5d76cad53f66f1312bd995909a5bab3c0820770 Russell Belfer <arrbee@arrbee.com> 1325281762 -0800 commit: more macro tests
diff --git a/tests/resources/attr/.gitted/logs/refs/heads/master b/tests/resources/attr/.gitted/logs/refs/heads/master
new file mode 100644
index 000000000..f518a465a
--- /dev/null
+++ b/tests/resources/attr/.gitted/logs/refs/heads/master
@@ -0,0 +1,3 @@
+0000000000000000000000000000000000000000 6bab5c79cd5140d0f800917f550eb2a3dc32b0da Russell Belfer <arrbee@arrbee.com> 1324416995 -0800 commit (initial): initial test data
+6bab5c79cd5140d0f800917f550eb2a3dc32b0da 605812ab7fe421fdd325a935d35cb06a9234a7d7 Russell Belfer <arrbee@arrbee.com> 1325143098 -0800 commit: latest test updates
+605812ab7fe421fdd325a935d35cb06a9234a7d7 a5d76cad53f66f1312bd995909a5bab3c0820770 Russell Belfer <arrbee@arrbee.com> 1325281762 -0800 commit: more macro tests
diff --git a/tests/resources/attr/.gitted/objects/29/29de282ce999e95183aedac6451d3384559c4b b/tests/resources/attr/.gitted/objects/29/29de282ce999e95183aedac6451d3384559c4b
new file mode 100644
index 000000000..ad84f0854
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/29/29de282ce999e95183aedac6451d3384559c4b
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/2b/40c5aca159b04ea8d20ffe36cdf8b09369b14a b/tests/resources/attr/.gitted/objects/2b/40c5aca159b04ea8d20ffe36cdf8b09369b14a
new file mode 100644
index 000000000..0e2368069
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/2b/40c5aca159b04ea8d20ffe36cdf8b09369b14a
@@ -0,0 +1 @@
+xmPj0=P8ZSc hR6{=ob"afv#3ά=7P%[8<He`&]@?aFZ@!.:ldLG|K7~XN8Id}q2cG7l5V_pE#lZGMt[J½&hu][4-3;Cg4x`ZYÌ錻b^> yNlͣ>c;gӐkYX9b|D~Vؗ)vܕ \ No newline at end of file
diff --git a/tests/resources/attr/.gitted/objects/2c/66e14f77196ea763fb1e41612c1aa2bc2d8ed2 b/tests/resources/attr/.gitted/objects/2c/66e14f77196ea763fb1e41612c1aa2bc2d8ed2
new file mode 100644
index 000000000..4b75d50eb
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/2c/66e14f77196ea763fb1e41612c1aa2bc2d8ed2
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/2d/e7dfe3588f3c7e9ad59e7d50ba90e3329df9d9 b/tests/resources/attr/.gitted/objects/2d/e7dfe3588f3c7e9ad59e7d50ba90e3329df9d9
new file mode 100644
index 000000000..e0fd0468e
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/2d/e7dfe3588f3c7e9ad59e7d50ba90e3329df9d9
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/3b/74db7ab381105dc0d28f8295a77f6a82989292 b/tests/resources/attr/.gitted/objects/3b/74db7ab381105dc0d28f8295a77f6a82989292
new file mode 100644
index 000000000..e5cef35fa
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/3b/74db7ab381105dc0d28f8295a77f6a82989292
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/3e/42ffc54a663f9401cc25843d6c0e71a33e4249 b/tests/resources/attr/.gitted/objects/3e/42ffc54a663f9401cc25843d6c0e71a33e4249
new file mode 100644
index 000000000..091d79b14
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/3e/42ffc54a663f9401cc25843d6c0e71a33e4249
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/45/141a79a77842c59a63229403220a4e4be74e3d b/tests/resources/attr/.gitted/objects/45/141a79a77842c59a63229403220a4e4be74e3d
new file mode 100644
index 000000000..5b58ef024
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/45/141a79a77842c59a63229403220a4e4be74e3d
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/55/6f8c827b8e4a02ad5cab77dca2bcb3e226b0b3 b/tests/resources/attr/.gitted/objects/55/6f8c827b8e4a02ad5cab77dca2bcb3e226b0b3
new file mode 100644
index 000000000..4bcff1faa
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/55/6f8c827b8e4a02ad5cab77dca2bcb3e226b0b3
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/58/19a185d77b03325aaf87cafc771db36f6ddca7 b/tests/resources/attr/.gitted/objects/58/19a185d77b03325aaf87cafc771db36f6ddca7
new file mode 100644
index 000000000..fe34eb63a
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/58/19a185d77b03325aaf87cafc771db36f6ddca7
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/60/5812ab7fe421fdd325a935d35cb06a9234a7d7 b/tests/resources/attr/.gitted/objects/60/5812ab7fe421fdd325a935d35cb06a9234a7d7
new file mode 100644
index 000000000..b0cc51ee6
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/60/5812ab7fe421fdd325a935d35cb06a9234a7d7
@@ -0,0 +1,2 @@
+xN 0;S˻BU J ?lٖygcáU RbacG;l㠝Dq֠ZʫAH<Ǒ3N=J2d3[0=
+}ۤI™jM"x/[TwU&[/k(tJL \ No newline at end of file
diff --git a/tests/resources/attr/.gitted/objects/6b/ab5c79cd5140d0f800917f550eb2a3dc32b0da b/tests/resources/attr/.gitted/objects/6b/ab5c79cd5140d0f800917f550eb2a3dc32b0da
new file mode 100644
index 000000000..f51e11ccc
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/6b/ab5c79cd5140d0f800917f550eb2a3dc32b0da
@@ -0,0 +1,3 @@
+x 0 E)@d'~@(#tQiQn޷(Pm"Ř2hs L+d{"{Z`u
+O4Y[޷;@>MSOmʧh
+* <- \ No newline at end of file
diff --git a/tests/resources/attr/.gitted/objects/94/da4faa0a6bfb8ee6ccf7153801a69202b31857 b/tests/resources/attr/.gitted/objects/94/da4faa0a6bfb8ee6ccf7153801a69202b31857
new file mode 100644
index 000000000..a9ddf5d20
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/94/da4faa0a6bfb8ee6ccf7153801a69202b31857
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/99/eae476896f4907224978b88e5ecaa6c5bb67a9 b/tests/resources/attr/.gitted/objects/99/eae476896f4907224978b88e5ecaa6c5bb67a9
new file mode 100644
index 000000000..8f5acc70a
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/99/eae476896f4907224978b88e5ecaa6c5bb67a9
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/9f/b40b6675dde60b5697afceae91b66d908c02d9 b/tests/resources/attr/.gitted/objects/9f/b40b6675dde60b5697afceae91b66d908c02d9
new file mode 100644
index 000000000..7663ad0ad
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/9f/b40b6675dde60b5697afceae91b66d908c02d9
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/a5/6bbcecaeac760cc26239384d2d4c614e7e4320 b/tests/resources/attr/.gitted/objects/a5/6bbcecaeac760cc26239384d2d4c614e7e4320
new file mode 100644
index 000000000..d898ae9b8
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/a5/6bbcecaeac760cc26239384d2d4c614e7e4320
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/a5/d76cad53f66f1312bd995909a5bab3c0820770 b/tests/resources/attr/.gitted/objects/a5/d76cad53f66f1312bd995909a5bab3c0820770
new file mode 100644
index 000000000..cd6a389f9
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/a5/d76cad53f66f1312bd995909a5bab3c0820770
@@ -0,0 +1,4 @@
+x]
+!E{vB>!"ZB;u3Cm {.7Z4avfgBLEeP;NQڬBLAnŲI 5I)M6ZQ[
+h3e:
+ }u};|)z&pbq?3TJ13JX \ No newline at end of file
diff --git a/tests/resources/attr/.gitted/objects/c0/091889c0c77142b87a1fa5123a6398a61d33e7 b/tests/resources/attr/.gitted/objects/c0/091889c0c77142b87a1fa5123a6398a61d33e7
new file mode 100644
index 000000000..11dc63c79
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/c0/091889c0c77142b87a1fa5123a6398a61d33e7
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/c4/85abe35abd4aa6fd83b076a78bbea9e2e7e06c b/tests/resources/attr/.gitted/objects/c4/85abe35abd4aa6fd83b076a78bbea9e2e7e06c
new file mode 100644
index 000000000..58569ca0e
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/c4/85abe35abd4aa6fd83b076a78bbea9e2e7e06c
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/c7/aadd770d5907a8475c29e9ee21a27b88bf675d b/tests/resources/attr/.gitted/objects/c7/aadd770d5907a8475c29e9ee21a27b88bf675d
new file mode 100644
index 000000000..39aedb7d9
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/c7/aadd770d5907a8475c29e9ee21a27b88bf675d
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/d5/7da33c16b14326ecb05d19bbea908f5e4c47d9 b/tests/resources/attr/.gitted/objects/d5/7da33c16b14326ecb05d19bbea908f5e4c47d9
new file mode 100644
index 000000000..b96d40c24
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/d5/7da33c16b14326ecb05d19bbea908f5e4c47d9
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/d8/00886d9c86731ae5c4a62b0b77c437015e00d2 b/tests/resources/attr/.gitted/objects/d8/00886d9c86731ae5c4a62b0b77c437015e00d2
new file mode 100644
index 000000000..83f3b726d
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/d8/00886d9c86731ae5c4a62b0b77c437015e00d2
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/dc/cada462d3df8ac6de596fb8c896aba9344f941 b/tests/resources/attr/.gitted/objects/dc/cada462d3df8ac6de596fb8c896aba9344f941
new file mode 100644
index 000000000..ef62f8b9d
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/dc/cada462d3df8ac6de596fb8c896aba9344f941
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/e5/63cf4758f0d646f1b14b76016aa17fa9e549a4 b/tests/resources/attr/.gitted/objects/e5/63cf4758f0d646f1b14b76016aa17fa9e549a4
new file mode 100644
index 000000000..1bc1f0f0b
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/e5/63cf4758f0d646f1b14b76016aa17fa9e549a4
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/f2/c6d717cf4a5a3e6b02684155ab07b766982165 b/tests/resources/attr/.gitted/objects/f2/c6d717cf4a5a3e6b02684155ab07b766982165
new file mode 100644
index 000000000..27a25dc86
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/f2/c6d717cf4a5a3e6b02684155ab07b766982165
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/fb/5067b1aef3ac1ada4b379dbcb7d17255df7d78 b/tests/resources/attr/.gitted/objects/fb/5067b1aef3ac1ada4b379dbcb7d17255df7d78
new file mode 100644
index 000000000..6c8ff837e
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/fb/5067b1aef3ac1ada4b379dbcb7d17255df7d78
Binary files differ
diff --git a/tests/resources/attr/.gitted/objects/ff/69f8639ce2e6010b3f33a74160aad98b48da2b b/tests/resources/attr/.gitted/objects/ff/69f8639ce2e6010b3f33a74160aad98b48da2b
new file mode 100644
index 000000000..b736c0b2b
--- /dev/null
+++ b/tests/resources/attr/.gitted/objects/ff/69f8639ce2e6010b3f33a74160aad98b48da2b
Binary files differ
diff --git a/tests/resources/attr/.gitted/refs/heads/master b/tests/resources/attr/.gitted/refs/heads/master
new file mode 100644
index 000000000..0516af2d2
--- /dev/null
+++ b/tests/resources/attr/.gitted/refs/heads/master
@@ -0,0 +1 @@
+a5d76cad53f66f1312bd995909a5bab3c0820770
diff --git a/tests/resources/attr/attr0 b/tests/resources/attr/attr0
new file mode 100644
index 000000000..556f8c827
--- /dev/null
+++ b/tests/resources/attr/attr0
@@ -0,0 +1 @@
+* binary
diff --git a/tests/resources/attr/attr1 b/tests/resources/attr/attr1
new file mode 100644
index 000000000..3b74db7ab
--- /dev/null
+++ b/tests/resources/attr/attr1
@@ -0,0 +1,29 @@
+# a comment followed by some blank lines
+
+
+
+ # another comment that is indented
+
+# variations on fnmatch
+
+pat0 attr0
+!pat1 attr1
+pat2/ attr2
+pat3dir/pat3file attr3
+pat4.* attr4
+ *.pat5 attr5
+pat6/pat6/*.pat6 attr6
+
+pat7[a-e]??[xyz] attr7 # with a comment on the line
+
+pat8\ with\ spaces attr8
+
+ invalid # attr with no assignments doesn't count
+
+also/invalid
+
+invalid.again/
+
+# next attr is at eof
+
+ pat9 attr9 \ No newline at end of file
diff --git a/tests/resources/attr/attr2 b/tests/resources/attr/attr2
new file mode 100644
index 000000000..2c66e14f7
--- /dev/null
+++ b/tests/resources/attr/attr2
@@ -0,0 +1,21 @@
+
+# variations on assignments
+
+pat0 simple
+pat1 -neg
+* notundef
+pat2 !notundef
+pat3 assigned=test-value
+pat4 rule-with-more-chars=value-with-more-chars
+pat5 empty=
+pat6 -negempty=
+pat7 multiple -single values=1 also=a-really-long-value/* happy=yes!
+# the next line has trailing spaces
+pat8 again= another=12321
+patbad0 # empty assignment does not count
+# next line will be another simple empty assign that should not count
+ patbad1
+
+# BTW I think there are 11 valid rules and two "invalid" empty ones
+
+pat9 -at-eof \ No newline at end of file
diff --git a/tests/resources/attr/attr3 b/tests/resources/attr/attr3
new file mode 100644
index 000000000..c485abe35
--- /dev/null
+++ b/tests/resources/attr/attr3
@@ -0,0 +1,4 @@
+# These are examples from the git-check-attr.1 man page
+*.java diff=java -crlf myAttr
+NoMyAttr.java !myAttr
+README caveat=unspecified
diff --git a/tests/resources/attr/binfile b/tests/resources/attr/binfile
new file mode 100644
index 000000000..d800886d9
--- /dev/null
+++ b/tests/resources/attr/binfile
@@ -0,0 +1 @@
+123 \ No newline at end of file
diff --git a/tests/resources/attr/gitattributes b/tests/resources/attr/gitattributes
new file mode 100644
index 000000000..2b40c5aca
--- /dev/null
+++ b/tests/resources/attr/gitattributes
@@ -0,0 +1,24 @@
+* rootattr
+root_test2 -rootattr
+root_test3 !rootattr
+binfile binary
+abc foo bar baz
+
+root_test2 multiattr
+root_test3 multi2=foo
+
+root_test3 multiattr=1 multiattr=2 multiattr=3 multi2=abc !multi2
+root_test2 multiattr=string -multiattr
+
+[attr]mymacro positive -negative !rootattr
+macro* mymacro another=77
+
+[attr]macro2 multi2 -multi2 multi3 !multi3 multi3=answer
+macro* macro2 macro2 macro2
+
+# let's try some malicious macro defs
+[attr]firstmacro -thirdmacro -secondmacro
+[attr]secondmacro firstmacro -firstmacro
+[attr]thirdmacro secondmacro=hahaha
+
+macro_bad firstmacro secondmacro thirdmacro
diff --git a/tests/resources/attr/macro_bad b/tests/resources/attr/macro_bad
new file mode 100644
index 000000000..5819a185d
--- /dev/null
+++ b/tests/resources/attr/macro_bad
@@ -0,0 +1 @@
+boo
diff --git a/tests/resources/attr/macro_test b/tests/resources/attr/macro_test
new file mode 100644
index 000000000..ff69f8639
--- /dev/null
+++ b/tests/resources/attr/macro_test
@@ -0,0 +1 @@
+Yo
diff --git a/tests/resources/attr/root_test1 b/tests/resources/attr/root_test1
new file mode 100644
index 000000000..45141a79a
--- /dev/null
+++ b/tests/resources/attr/root_test1
@@ -0,0 +1 @@
+Hello from the root
diff --git a/tests/resources/attr/root_test2 b/tests/resources/attr/root_test2
new file mode 100644
index 000000000..45141a79a
--- /dev/null
+++ b/tests/resources/attr/root_test2
@@ -0,0 +1 @@
+Hello from the root
diff --git a/tests/resources/attr/root_test3 b/tests/resources/attr/root_test3
new file mode 100644
index 000000000..45141a79a
--- /dev/null
+++ b/tests/resources/attr/root_test3
@@ -0,0 +1 @@
+Hello from the root
diff --git a/tests/resources/attr/root_test4.txt b/tests/resources/attr/root_test4.txt
new file mode 100644
index 000000000..fb5067b1a
--- /dev/null
+++ b/tests/resources/attr/root_test4.txt
@@ -0,0 +1 @@
+Hello again
diff --git a/tests/resources/attr/subdir/.gitattributes b/tests/resources/attr/subdir/.gitattributes
new file mode 100644
index 000000000..99eae4768
--- /dev/null
+++ b/tests/resources/attr/subdir/.gitattributes
@@ -0,0 +1,5 @@
+* subattr=yes -negattr
+subdir/*.txt another=one
+ab* merge=filfre
+abc -foo -bar
+*.c frotz
diff --git a/tests/resources/attr/subdir/abc b/tests/resources/attr/subdir/abc
new file mode 100644
index 000000000..3e42ffc54
--- /dev/null
+++ b/tests/resources/attr/subdir/abc
@@ -0,0 +1,37 @@
+# Test file from gitattributes(5) example:
+
+If you have these three gitattributes file:
+
+ (in $GIT_DIR/info/attributes)
+
+ a* foo !bar -baz
+
+ (in .gitattributes)
+ abc foo bar baz
+
+ (in t/.gitattributes)
+ ab* merge=filfre
+ abc -foo -bar
+ *.c frotz
+
+the attributes given to path t/abc are computed as follows:
+
+1. By examining t/.gitattributes (which is in the same directory as the path
+ in question), git finds that the first line matches. merge attribute is
+ set. It also finds that the second line matches, and attributes foo and
+ bar are unset.
+2. Then it examines .gitattributes (which is in the parent directory), and
+ finds that the first line matches, but t/.gitattributes file already
+ decided how merge, foo and bar attributes should be given to this path,
+ so it leaves foo and bar unset. Attribute baz is set.
+3. Finally it examines $GIT_DIR/info/attributes. This file is used to
+ override the in-tree settings. The first line is a match, and foo is set,
+ bar is reverted to unspecified state, and baz is unset.
+
+As the result, the attributes assignment to t/abc becomes:
+
+ foo set to true
+ bar unspecified
+ baz set to false
+ merge set to string value "filfre"
+ frotz unspecified
diff --git a/tests/resources/attr/subdir/subdir_test1 b/tests/resources/attr/subdir/subdir_test1
new file mode 100644
index 000000000..e563cf475
--- /dev/null
+++ b/tests/resources/attr/subdir/subdir_test1
@@ -0,0 +1,2 @@
+Hello from the subdir
+
diff --git a/tests/resources/attr/subdir/subdir_test2.txt b/tests/resources/attr/subdir/subdir_test2.txt
new file mode 100644
index 000000000..fb5067b1a
--- /dev/null
+++ b/tests/resources/attr/subdir/subdir_test2.txt
@@ -0,0 +1 @@
+Hello again
diff --git a/tests/resources/attr/subdir2/subdir2_test1 b/tests/resources/attr/subdir2/subdir2_test1
new file mode 100644
index 000000000..dccada462
--- /dev/null
+++ b/tests/resources/attr/subdir2/subdir2_test1
@@ -0,0 +1 @@
+Hello from subdir2