]> git.proxmox.com Git - libgit2.git/commitdiff
Attribute file cache refactor
authorRussell Belfer <rb@github.com>
Fri, 11 Apr 2014 05:31:01 +0000 (22:31 -0700)
committerRussell Belfer <rb@github.com>
Thu, 17 Apr 2014 21:56:41 +0000 (14:56 -0700)
This is a big refactoring of the attribute file cache to be a bit
simpler which in turn makes it easier to enforce a lock around any
updates to the cache so that it can be used in a threaded env.
Tons of changes to the attributes and ignores code.

18 files changed:
src/attr.c
src/attr.h
src/attr_file.c
src/attr_file.h
src/attrcache.c [new file with mode: 0644]
src/attrcache.h
src/fileops.c
src/fileops.h
src/ignore.c
src/index.c
src/sortedcache.c
src/submodule.c
tests/attr/file.c
tests/attr/lookup.c
tests/threads/diff.c
tests/threads/iterator.c [new file with mode: 0644]
tests/threads/thread_helpers.c [new file with mode: 0644]
tests/threads/thread_helpers.h [new file with mode: 0644]

index f52a8a97b6c98d61a8c33c50f3385713d8ce7077..c53a728de5115a55b5cf7d02e689ce0ab3d4cbf5 100644 (file)
@@ -2,7 +2,7 @@
 #include "repository.h"
 #include "sysdir.h"
 #include "config.h"
-#include "attr.h"
+#include "attr_file.h"
 #include "ignore.h"
 #include "git2/oid.h"
 #include <ctype.h>
@@ -216,7 +216,6 @@ cleanup:
        return error;
 }
 
-
 int git_attr_add_macro(
        git_repository *repo,
        const char *name,
@@ -251,261 +250,6 @@ int git_attr_add_macro(
        return error;
 }
 
-bool git_attr_cache__is_cached(
-       git_repository *repo, git_attr_file_source source, const char *path)
-{
-       git_buf cache_key = GIT_BUF_INIT;
-       git_strmap *files = git_repository_attr_cache(repo)->files;
-       const char *workdir = git_repository_workdir(repo);
-       bool rval;
-
-       if (workdir && git__prefixcmp(path, workdir) == 0)
-               path += strlen(workdir);
-       if (git_buf_printf(&cache_key, "%d#%s", (int)source, path) < 0)
-               return false;
-
-       rval = git_strmap_exists(files, git_buf_cstr(&cache_key));
-
-       git_buf_free(&cache_key);
-
-       return rval;
-}
-
-static int load_attr_file(
-       const char **data,
-       git_futils_filestamp *stamp,
-       const char *filename)
-{
-       int error;
-       git_buf content = GIT_BUF_INIT;
-
-       error = git_futils_filestamp_check(stamp, filename);
-       if (error < 0)
-               return error;
-
-       /* if error == 0, then file is up to date. By returning GIT_ENOTFOUND,
-        * we tell the caller not to reparse this file...
-        */
-       if (!error)
-               return GIT_ENOTFOUND;
-
-       error = git_futils_readbuffer(&content, filename);
-       if (error < 0) {
-               /* convert error into ENOTFOUND so failed permissions / invalid
-                * file type don't actually stop the operation in progress.
-                */
-               return GIT_ENOTFOUND;
-
-               /* TODO: once warnings are available, issue a warning callback */
-       }
-
-       *data = git_buf_detach(&content);
-
-       return 0;
-}
-
-static int load_attr_blob_from_index(
-       const char **content,
-       git_blob **blob,
-       git_repository *repo,
-       const git_oid *old_oid,
-       const char *relfile)
-{
-       int error;
-       size_t pos;
-       git_index *index;
-       const git_index_entry *entry;
-
-       if ((error = git_repository_index__weakptr(&index, repo)) < 0 ||
-               (error = git_index_find(&pos, index, relfile)) < 0)
-               return error;
-
-       entry = git_index_get_byindex(index, pos);
-
-       if (old_oid && git_oid__cmp(old_oid, &entry->id) == 0)
-               return GIT_ENOTFOUND;
-
-       if ((error = git_blob_lookup(blob, repo, &entry->id)) < 0)
-               return error;
-
-       *content = git_blob_rawcontent(*blob);
-       return 0;
-}
-
-static int load_attr_from_cache(
-       git_attr_file **file,
-       git_attr_cache *cache,
-       git_attr_file_source source,
-       const char *relative_path)
-{
-       git_buf  cache_key = GIT_BUF_INIT;
-       khiter_t cache_pos;
-
-       *file = NULL;
-
-       if (!cache || !cache->files)
-               return 0;
-
-       if (git_buf_printf(&cache_key, "%d#%s", (int)source, relative_path) < 0)
-               return -1;
-
-       if (git_mutex_lock(&cache->lock) < 0) {
-               giterr_set(GITERR_OS, "Could not get cache attr lock");
-               git_buf_free(&cache_key);
-               return -1;
-       }
-
-       cache_pos = git_strmap_lookup_index(cache->files, cache_key.ptr);
-
-       if (git_strmap_valid_index(cache->files, cache_pos)) {
-               *file = git_strmap_value_at(cache->files, cache_pos);
-               GIT_REFCOUNT_INC(*file);
-       }
-
-       git_mutex_unlock(&cache->lock);
-       git_buf_free(&cache_key);
-
-       return 0;
-}
-
-int git_attr_cache__internal_file(
-       git_repository *repo,
-       const char *filename,
-       git_attr_file **file)
-{
-       int error = 0;
-       git_attr_cache *cache = git_repository_attr_cache(repo);
-       khiter_t cache_pos;
-
-       if (git_mutex_lock(&cache->lock) < 0) {
-               giterr_set(GITERR_OS, "Unable to get attr cache lock");
-               return -1;
-       }
-
-       cache_pos = git_strmap_lookup_index(cache->files, filename);
-
-       if (git_strmap_valid_index(cache->files, cache_pos)) {
-               *file = git_strmap_value_at(cache->files, cache_pos);
-       }
-       else if (!(error = git_attr_file__new(file, 0, filename, &cache->pool))) {
-
-               git_strmap_insert(cache->files, (*file)->key + 2, *file, error);
-               if (error > 0)
-                       error = 0;
-       }
-
-       git_mutex_unlock(&cache->lock);
-       return error;
-}
-
-int git_attr_cache__push_file(
-       git_repository *repo,
-       const char *base,
-       const char *filename,
-       git_attr_file_source source,
-       git_attr_file_parser parse,
-       void* parsedata,
-       git_vector *stack)
-{
-       int error = 0;
-       git_buf path = GIT_BUF_INIT;
-       const char *workdir = git_repository_workdir(repo);
-       const char *relfile, *content = NULL;
-       git_attr_cache *cache = git_repository_attr_cache(repo);
-       git_attr_file *file = NULL;
-       git_blob *blob = NULL;
-       git_futils_filestamp stamp;
-
-       assert(filename && stack);
-
-       /* join base and path as needed */
-       if (base != NULL && git_path_root(filename) < 0) {
-               if (git_buf_joinpath(&path, base, filename) < 0)
-                       return -1;
-               filename = path.ptr;
-       }
-
-       relfile = filename;
-       if (workdir && git__prefixcmp(relfile, workdir) == 0)
-               relfile += strlen(workdir);
-
-       /* check cache */
-       if (load_attr_from_cache(&file, cache, source, relfile) < 0)
-               return -1;
-
-       /* if not in cache, load data, parse, and cache */
-
-       if (source == GIT_ATTR_FILE_FROM_FILE) {
-               git_futils_filestamp_set(
-                       &stamp, file ? &file->cache_data.stamp : NULL);
-
-               error = load_attr_file(&content, &stamp, filename);
-       } else {
-               error = load_attr_blob_from_index(&content, &blob,
-                       repo, file ? &file->cache_data.oid : NULL, relfile);
-       }
-
-       if (error) {
-               /* not finding a file is not an error for this function */
-               if (error == GIT_ENOTFOUND) {
-                       giterr_clear();
-                       error = 0;
-               }
-               goto finish;
-       }
-
-       /* if we got here, we have to parse and/or reparse the file */
-       if (file)
-               git_attr_file__clear_rules(file);
-       else {
-               error = git_attr_file__new(&file, source, relfile, &cache->pool);
-               if (error < 0)
-                       goto finish;
-       }
-
-       if (parse && (error = parse(repo, parsedata, content, file)) < 0)
-               goto finish;
-
-       if (git_mutex_lock(&cache->lock) < 0) {
-               giterr_set(GITERR_OS, "Unable to get attr cache lock");
-               error = -1;
-       } else {
-               git_strmap_insert(cache->files, file->key, file, error); /* -V595 */
-               if (error > 0) { /* > 0 means inserting for the first time */
-                       error = 0;
-                       GIT_REFCOUNT_INC(file);
-               }
-               git_mutex_unlock(&cache->lock);
-       }
-
-       /* remember "cache buster" file signature */
-       if (blob)
-               git_oid_cpy(&file->cache_data.oid, git_object_id((git_object *)blob));
-       else
-               git_futils_filestamp_set(&file->cache_data.stamp, &stamp);
-
-finish:
-       /* push file onto vector if we found one*/
-       if (!error && file != NULL)
-               error = git_vector_insert(stack, file);
-
-       if (error != 0)
-               git_attr_file__free(file);
-
-       if (blob)
-               git_blob_free(blob);
-       else
-               git__free((void *)content);
-
-       git_buf_free(&path);
-
-       return error;
-}
-
-#define push_attr_file(R,S,B,F) \
-       git_attr_cache__push_file                                                                                       \
-       ((R),(B),(F),GIT_ATTR_FILE_FROM_FILE,git_attr_file__parse_buffer,NULL,(S))
-
 typedef struct {
        git_repository *repo;
        uint32_t flags;
@@ -514,46 +258,64 @@ typedef struct {
        git_vector *files;
 } attr_walk_up_info;
 
-int git_attr_cache__decide_sources(
-       uint32_t flags, bool has_wd, bool has_index, git_attr_file_source *srcs)
+static int attr_decide_sources(
+       uint32_t flags, bool has_wd, bool has_index, git_attr_cache_source *srcs)
 {
        int count = 0;
 
        switch (flags & 0x03) {
        case GIT_ATTR_CHECK_FILE_THEN_INDEX:
                if (has_wd)
-                       srcs[count++] = GIT_ATTR_FILE_FROM_FILE;
+                       srcs[count++] = GIT_ATTR_CACHE__FROM_FILE;
                if (has_index)
-                       srcs[count++] = GIT_ATTR_FILE_FROM_INDEX;
+                       srcs[count++] = GIT_ATTR_CACHE__FROM_INDEX;
                break;
        case GIT_ATTR_CHECK_INDEX_THEN_FILE:
                if (has_index)
-                       srcs[count++] = GIT_ATTR_FILE_FROM_INDEX;
+                       srcs[count++] = GIT_ATTR_CACHE__FROM_INDEX;
                if (has_wd)
-                       srcs[count++] = GIT_ATTR_FILE_FROM_FILE;
+                       srcs[count++] = GIT_ATTR_CACHE__FROM_FILE;
                break;
        case GIT_ATTR_CHECK_INDEX_ONLY:
                if (has_index)
-                       srcs[count++] = GIT_ATTR_FILE_FROM_INDEX;
+                       srcs[count++] = GIT_ATTR_CACHE__FROM_INDEX;
                break;
        }
 
        return count;
 }
 
+static int push_attr_file(
+       git_repository *repo,
+       git_vector *list,
+       git_attr_cache_source source,
+       const char *base,
+       const char *filename)
+{
+       int error = 0;
+       git_attr_file *file = NULL;
+
+       if ((error = git_attr_cache__get(
+                       &file, repo, source, base, filename,
+                       git_attr_file__parse_buffer, NULL)) < 0 ||
+               (error = git_vector_insert(list, file)) < 0)
+               git_attr_file__free(file);
+
+       return error;
+}
+
 static int push_one_attr(void *ref, git_buf *path)
 {
        int error = 0, n_src, i;
        attr_walk_up_info *info = (attr_walk_up_info *)ref;
-       git_attr_file_source src[2];
+       git_attr_cache_source src[2];
 
-       n_src = git_attr_cache__decide_sources(
+       n_src = attr_decide_sources(
                info->flags, info->workdir != NULL, info->index != NULL, src);
 
        for (i = 0; !error && i < n_src; ++i)
-               error = git_attr_cache__push_file(
-                       info->repo, path->ptr, GIT_ATTR_FILE, src[i],
-                       git_attr_file__parse_buffer, NULL, info->files);
+               error = push_attr_file(
+                       info->repo, info->files, src[i], path->ptr, GIT_ATTR_FILE);
 
        return error;
 }
@@ -601,7 +363,8 @@ static int collect_attr_files(
         */
 
        error = push_attr_file(
-               repo, files, git_repository_path(repo), GIT_ATTR_FILE_INREPO);
+               repo, files, GIT_ATTR_CACHE__FROM_FILE,
+               git_repository_path(repo), GIT_ATTR_FILE_INREPO);
        if (error < 0)
                goto cleanup;
 
@@ -618,7 +381,8 @@ static int collect_attr_files(
 
        if (git_repository_attr_cache(repo)->cfg_attr_file != NULL) {
                error = push_attr_file(
-                       repo, files, NULL, git_repository_attr_cache(repo)->cfg_attr_file);
+                       repo, files, GIT_ATTR_CACHE__FROM_FILE,
+                       NULL, git_repository_attr_cache(repo)->cfg_attr_file);
                if (error < 0)
                        goto cleanup;
        }
@@ -626,7 +390,8 @@ static int collect_attr_files(
        if ((flags & GIT_ATTR_CHECK_NO_SYSTEM) == 0) {
                error = git_sysdir_find_system_file(&dir, GIT_ATTR_FILE_SYSTEM);
                if (!error)
-                       error = push_attr_file(repo, files, NULL, dir.ptr);
+                       error = push_attr_file(
+                               repo, files, GIT_ATTR_CACHE__FROM_FILE, NULL, dir.ptr);
                else if (error == GIT_ENOTFOUND) {
                        giterr_clear();
                        error = 0;
@@ -640,172 +405,3 @@ static int collect_attr_files(
 
        return error;
 }
-
-static int attr_cache__lookup_path(
-       char **out, git_config *cfg, const char *key, const char *fallback)
-{
-       git_buf buf = GIT_BUF_INIT;
-       int error;
-       const git_config_entry *entry = NULL;
-
-       *out = NULL;
-
-       if ((error = git_config__lookup_entry(&entry, cfg, key, false)) < 0)
-               return error;
-
-       if (entry) {
-               const char *cfgval = entry->value;
-
-               /* expand leading ~/ as needed */
-               if (cfgval && cfgval[0] == '~' && cfgval[1] == '/' &&
-                       !git_sysdir_find_global_file(&buf, &cfgval[2]))
-                       *out = git_buf_detach(&buf);
-               else if (cfgval)
-                       *out = git__strdup(cfgval);
-
-       }
-       else if (!git_sysdir_find_xdg_file(&buf, fallback))
-               *out = git_buf_detach(&buf);
-
-       git_buf_free(&buf);
-
-       return error;
-}
-
-static void attr_cache__free(git_attr_cache *cache)
-{
-       if (!cache)
-               return;
-
-       if (cache->files != NULL) {
-               git_attr_file *file;
-
-               git_strmap_foreach_value(cache->files, file, {
-                       git_attr_file__free(file);
-               });
-
-               git_strmap_free(cache->files);
-       }
-
-       if (cache->macros != NULL) {
-               git_attr_rule *rule;
-
-               git_strmap_foreach_value(cache->macros, rule, {
-                       git_attr_rule__free(rule);
-               });
-
-               git_strmap_free(cache->macros);
-       }
-
-       git_pool_clear(&cache->pool);
-
-       git__free(cache->cfg_attr_file);
-       cache->cfg_attr_file = NULL;
-
-       git__free(cache->cfg_excl_file);
-       cache->cfg_excl_file = NULL;
-
-       git_mutex_free(&cache->lock);
-
-       git__free(cache);
-}
-
-int git_attr_cache__init(git_repository *repo)
-{
-       int ret = 0;
-       git_attr_cache *cache = git_repository_attr_cache(repo);
-       git_config *cfg;
-
-       if (cache)
-               return 0;
-
-       if ((ret = git_repository_config__weakptr(&cfg, repo)) < 0)
-               return ret;
-
-       cache = git__calloc(1, sizeof(git_attr_cache));
-       GITERR_CHECK_ALLOC(cache);
-
-       /* set up lock */
-       if (git_mutex_init(&cache->lock) < 0) {
-               giterr_set(GITERR_OS, "Unable to initialize lock for attr cache");
-               git__free(cache);
-               return -1;
-       }
-
-       /* cache config settings for attributes and ignores */
-       ret = attr_cache__lookup_path(
-               &cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG, GIT_ATTR_FILE_XDG);
-       if (ret < 0)
-               goto cancel;
-
-       ret = attr_cache__lookup_path(
-               &cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG, GIT_IGNORE_FILE_XDG);
-       if (ret < 0)
-               goto cancel;
-
-       /* allocate hashtable for attribute and ignore file contents,
-        * hashtable for attribute macros, and string pool
-        */
-       if ((ret = git_strmap_alloc(&cache->files)) < 0 ||
-               (ret = git_strmap_alloc(&cache->macros)) < 0 ||
-               (ret = git_pool_init(&cache->pool, 1, 0)) < 0)
-               goto cancel;
-
-       cache = git__compare_and_swap(&repo->attrcache, NULL, cache);
-       if (cache)
-               goto cancel; /* raced with another thread, free this but no error */
-
-       /* insert default macros */
-       return git_attr_add_macro(repo, "binary", "-diff -crlf -text");
-
-cancel:
-       attr_cache__free(cache);
-       return ret;
-}
-
-void git_attr_cache_flush(git_repository *repo)
-{
-       git_attr_cache *cache;
-
-       /* this could be done less expensively, but for now, we'll just free
-        * the entire attrcache and let the next use reinitialize it...
-        */
-       if (repo && (cache = git__swap(repo->attrcache, NULL)) != NULL)
-               attr_cache__free(cache);
-}
-
-int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro)
-{
-       git_attr_cache *cache = git_repository_attr_cache(repo);
-       git_strmap *macros = cache->macros;
-       int error;
-
-       /* TODO: generate warning log if (macro->assigns.length == 0) */
-       if (macro->assigns.length == 0)
-               return 0;
-
-       if (git_mutex_lock(&cache->lock) < 0) {
-               giterr_set(GITERR_OS, "Unable to get attr cache lock");
-               error = -1;
-       } else {
-               git_strmap_insert(macros, macro->match.pattern, macro, error);
-               git_mutex_unlock(&cache->lock);
-       }
-
-       return (error < 0) ? -1 : 0;
-}
-
-git_attr_rule *git_attr_cache__lookup_macro(
-       git_repository *repo, const char *name)
-{
-       git_strmap *macros = git_repository_attr_cache(repo)->macros;
-       khiter_t pos;
-
-       pos = git_strmap_lookup_index(macros, name);
-
-       if (!git_strmap_valid_index(macros, pos))
-               return NULL;
-
-       return (git_attr_rule *)git_strmap_value_at(macros, pos);
-}
-
index 19c979bcd3b4d9157b57662ae8970327e3f2565e..f9f216d07722588e23b6d3fdf3cd67991588a37f 100644 (file)
@@ -8,38 +8,6 @@
 #define INCLUDE_attr_h__
 
 #include "attr_file.h"
-
-#define GIT_ATTR_CONFIG       "core.attributesfile"
-#define GIT_IGNORE_CONFIG     "core.excludesfile"
-
-typedef int (*git_attr_file_parser)(
-       git_repository *, void *, const char *, git_attr_file *);
-
-extern int git_attr_cache__insert_macro(
-       git_repository *repo, git_attr_rule *macro);
-
-extern git_attr_rule *git_attr_cache__lookup_macro(
-       git_repository *repo, const char *name);
-
-extern int git_attr_cache__push_file(
-       git_repository *repo,
-       const char *base,
-       const char *filename,
-       git_attr_file_source source,
-       git_attr_file_parser parse,
-       void *parsedata, /* passed through to parse function */
-       git_vector *stack);
-
-extern int git_attr_cache__internal_file(
-       git_repository *repo,
-       const char *key,
-       git_attr_file **file_ptr);
-
-/* returns true if path is in cache */
-extern bool git_attr_cache__is_cached(
-       git_repository *repo, git_attr_file_source source, const char *path);
-
-extern int git_attr_cache__decide_sources(
-       uint32_t flags, bool has_wd, bool has_index, git_attr_file_source *srcs);
+#include "attrcache.h"
 
 #endif
index 695f661a8d27417f0801fa941d37e89fcb8867c4..86b3448ee035a72c3d2c649c566dd5f9dac39a70 100644 (file)
 #include "common.h"
 #include "repository.h"
 #include "filebuf.h"
-#include "attr.h"
+#include "attr_file.h"
 #include "git2/blob.h"
 #include "git2/tree.h"
+#include "index.h"
 #include <ctype.h>
 
-static int sort_by_hash_and_name(const void *a_raw, const void *b_raw);
-static void git_attr_rule__clear(git_attr_rule *rule);
-static bool parse_optimized_patterns(
-       git_attr_fnmatch *spec,
-       git_pool *pool,
-       const char *pattern);
+static void attr_file_free(git_attr_file *file)
+{
+       git_attr_file__clear_rules(file);
+       git_pool_clear(&file->pool);
+       git__memzero(file, sizeof(*file));
+       git__free(file);
+}
 
 int git_attr_file__new(
-       git_attr_file **attrs_ptr,
-       git_attr_file_source from,
-       const char *path,
-       git_pool *pool)
+       git_attr_file **out,
+       git_attr_cache_entry *ce,
+       git_attr_cache_source source)
 {
-       git_attr_file *attrs = NULL;
-
-       attrs = git__calloc(1, sizeof(git_attr_file));
+       git_attr_file *attrs = git__calloc(1, sizeof(git_attr_file));
        GITERR_CHECK_ALLOC(attrs);
-       GIT_REFCOUNT_INC(attrs);
 
-       if (pool)
-               attrs->pool = pool;
-       else {
-               attrs->pool = git__calloc(1, sizeof(git_pool));
-               if (!attrs->pool || git_pool_init(attrs->pool, 1, 0) < 0)
-                       goto fail;
-               attrs->pool_is_allocated = true;
+       if (git_pool_init(&attrs->pool, 1, 0) < 0 ||
+               git_vector_init(&attrs->rules, 0, NULL) < 0)
+       {
+               attr_file_free(attrs);
+               return -1;
        }
 
-       if (path) {
-               size_t len = strlen(path);
+       GIT_REFCOUNT_INC(attrs);
+       attrs->ce = ce;
+       attrs->source = source;
+       *out = attrs;
+       return 0;
+}
 
-               attrs->key = git_pool_malloc(attrs->pool, (uint32_t)len + 3);
-               GITERR_CHECK_ALLOC(attrs->key);
+void git_attr_file__clear_rules(git_attr_file *file)
+{
+       unsigned int i;
+       git_attr_rule *rule;
 
-               attrs->key[0] = '0' + (char)from;
-               attrs->key[1] = '#';
-               memcpy(&attrs->key[2], path, len);
-               attrs->key[len + 2] = '\0';
-       }
+       git_vector_foreach(&file->rules, i, rule)
+               git_attr_rule__free(rule);
+       git_vector_free(&file->rules);
+}
+
+void git_attr_file__free(git_attr_file *file)
+{
+       if (!file)
+               return;
+       GIT_REFCOUNT_DEC(file, attr_file_free);
+}
+
+static int attr_file_oid_from_index(
+       git_oid *oid, git_repository *repo, const char *path)
+{
+       int error;
+       git_index *idx;
+       size_t pos;
+       const git_index_entry *entry;
 
-       if (git_vector_init(&attrs->rules, 4, NULL) < 0)
-               goto fail;
+       if ((error = git_repository_index__weakptr(&idx, repo)) < 0 ||
+               (error = git_index__find_pos(&pos, idx, path, 0, 0)) < 0)
+               return error;
 
-       *attrs_ptr = attrs;
+       if (!(entry = git_index_get_byindex(idx, pos)))
+               return GIT_ENOTFOUND;
+
+       *oid = entry->id;
        return 0;
+}
+
+int git_attr_file__load(
+       git_attr_file **out,
+       git_repository *repo,
+       git_attr_cache_entry *ce,
+       git_attr_cache_source source,
+       git_attr_cache_parser parser,
+       void *payload)
+{
+       int error = 0;
+       git_blob *blob = NULL;
+       git_buf content = GIT_BUF_INIT;
+       const char *data = NULL;
+       git_attr_file *file;
 
-fail:
-       git_attr_file__free(attrs);
-       attrs_ptr = NULL;
-       return -1;
+       *out = NULL;
+
+       if (source == GIT_ATTR_CACHE__FROM_INDEX) {
+               git_oid id;
+
+               if ((error = attr_file_oid_from_index(&id, repo, ce->path)) < 0 ||
+                       (error = git_blob_lookup(&blob, repo, &id)) < 0)
+                       return error;
+
+               data = git_blob_rawcontent(blob);
+       } else {
+               if ((error = git_futils_readbuffer(&content, ce->fullpath)) < 0)
+                       /* always return ENOTFOUND so item will just be skipped */
+                       /* TODO: issue a warning once warnings API is available */
+                       return GIT_ENOTFOUND;
+               data = content.ptr;
+       }
+
+       if ((error = git_attr_file__new(&file, ce, source)) < 0)
+               goto cleanup;
+
+       if (parser && (error = parser(repo, file, data, payload)) < 0)
+               git_attr_file__free(file);
+       else
+               *out = file;
+
+cleanup:
+       git_blob_free(blob);
+       git_buf_free(&content);
+
+       return error;
+}
+
+int git_attr_file__out_of_date(git_repository *repo, git_attr_file *file)
+{
+       if (!file)
+               return 1;
+
+       if (file->source == GIT_ATTR_CACHE__FROM_INDEX) {
+               int error;
+               git_oid id;
+
+               if ((error = attr_file_oid_from_index(&id, repo, file->ce->path)) < 0)
+                       return error;
+
+               return (git_oid__cmp(&file->cache_data.oid, &id) != 0);
+       }
+
+       return git_futils_filestamp_check(
+               &file->cache_data.stamp, file->ce->fullpath);
 }
 
+static int sort_by_hash_and_name(const void *a_raw, const void *b_raw);
+static void git_attr_rule__clear(git_attr_rule *rule);
+static bool parse_optimized_patterns(
+       git_attr_fnmatch *spec,
+       git_pool *pool,
+       const char *pattern);
+
 int git_attr_file__parse_buffer(
-       git_repository *repo, void *parsedata, const char *buffer, git_attr_file *attrs)
+       git_repository *repo,
+       git_attr_file *attrs,
+       const char *data,
+       void *payload)
 {
        int error = 0;
-       const char *scan = NULL, *context = NULL;
+       const char *scan = data, *context = NULL;
        git_attr_rule *rule = NULL;
 
-       GIT_UNUSED(parsedata);
-
-       assert(buffer && attrs);
-
-       scan = buffer;
+       GIT_UNUSED(payload);
 
        /* if subdir file path, convert context for file paths */
-       if (attrs->key &&
-               git_path_root(attrs->key + 2) < 0 &&
-               git__suffixcmp(attrs->key, "/" GIT_ATTR_FILE) == 0)
-               context = attrs->key + 2;
+       if (attrs->ce &&
+               git_path_root(attrs->ce->path) < 0 &&
+               !git__suffixcmp(attrs->ce->path, "/" GIT_ATTR_FILE))
+               context = attrs->ce->path;
 
        while (!error && *scan) {
                /* allocate rule if needed */
                if (!rule) {
-                       if (!(rule = git__calloc(1, sizeof(git_attr_rule)))) {
+                       if (!(rule = git__calloc(1, sizeof(*rule)))) {
                                error = -1;
                                break;
                        }
@@ -90,9 +177,9 @@ int git_attr_file__parse_buffer(
 
                /* parse the next "pattern attr attr attr" line */
                if (!(error = git_attr_fnmatch__parse(
-                               &rule->match, attrs->pool, context, &scan)) &&
+                               &rule->match, &attrs->pool, context, &scan)) &&
                        !(error = git_attr_assignment__parse(
-                               repo, attrs->pool, &rule->assigns, &scan)))
+                               repo, &attrs->pool, &rule->assigns, &scan)))
                {
                        if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO)
                                /* should generate error/warning if this is coming from any
@@ -118,61 +205,6 @@ int git_attr_file__parse_buffer(
        return error;
 }
 
-int git_attr_file__new_and_load(
-       git_attr_file **attrs_ptr,
-       const char *path)
-{
-       int error;
-       git_buf content = GIT_BUF_INIT;
-
-       if ((error = git_attr_file__new(attrs_ptr, 0, path, NULL)) < 0)
-               return error;
-
-       if (!(error = git_futils_readbuffer(&content, path)))
-               error = git_attr_file__parse_buffer(
-                       NULL, NULL, git_buf_cstr(&content), *attrs_ptr);
-
-       git_buf_free(&content);
-
-       if (error) {
-               git_attr_file__free(*attrs_ptr);
-               *attrs_ptr = NULL;
-       }
-
-       return error;
-}
-
-void git_attr_file__clear_rules(git_attr_file *file)
-{
-       unsigned int i;
-       git_attr_rule *rule;
-
-       git_vector_foreach(&file->rules, i, rule)
-               git_attr_rule__free(rule);
-
-       git_vector_free(&file->rules);
-}
-
-static void attr_file_free(git_attr_file *file)
-{
-       git_attr_file__clear_rules(file);
-
-       if (file->pool_is_allocated) {
-               git_pool_clear(file->pool);
-               git__free(file->pool);
-       }
-       file->pool = NULL;
-
-       git__free(file);
-}
-
-void git_attr_file__free(git_attr_file *file)
-{
-       if (!file)
-               return;
-       GIT_REFCOUNT_DEC(file, attr_file_free);
-}
-
 uint32_t git_attr_file__name_hash(const char *name)
 {
        uint32_t h = 5381;
@@ -183,7 +215,6 @@ uint32_t git_attr_file__name_hash(const char *name)
        return h;
 }
 
-
 int git_attr_file__lookup_one(
        git_attr_file *file,
        const git_attr_path *path,
@@ -212,25 +243,64 @@ int git_attr_file__lookup_one(
        return 0;
 }
 
+int git_attr_file__load_standalone(
+       git_attr_file **out,
+       const char *path)
+{
+       int error;
+       git_attr_file *file;
+       git_buf content = GIT_BUF_INIT;
+
+       error = git_attr_file__new(&file, NULL, GIT_ATTR_CACHE__FROM_FILE);
+       if (error < 0)
+               return error;
+
+       error = git_attr_cache_entry__new(&file->ce, NULL, path, &file->pool);
+       if (error < 0) {
+               git_attr_file__free(file);
+               return error;
+       }
+       /* because the cache entry is allocated from the file's own pool, we
+        * don't have to free it - freeing file+pool will free cache entry, too.
+        */
+
+       if (!(error = git_futils_readbuffer(&content, path))) {
+               error = git_attr_file__parse_buffer(NULL, file, content.ptr, NULL);
+               git_buf_free(&content);
+       }
+
+       if (error < 0)
+               git_attr_file__free(file);
+       else
+               *out = file;
+
+       return error;
+}
 
 bool git_attr_fnmatch__match(
        git_attr_fnmatch *match,
        const git_attr_path *path)
 {
-       int fnm;
-       int icase_flags = (match->flags & GIT_ATTR_FNMATCH_ICASE) ? FNM_CASEFOLD : 0;
+       const char *filename;
+       int flags = 0;
 
-       if (match->flags & GIT_ATTR_FNMATCH_DIRECTORY && !path->is_dir)
+       if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir)
                return false;
 
-       if (match->flags & GIT_ATTR_FNMATCH_FULLPATH)
-               fnm = p_fnmatch(match->pattern, path->path, FNM_PATHNAME | icase_flags);
-       else if (path->is_dir)
-               fnm = p_fnmatch(match->pattern, path->basename, FNM_LEADING_DIR | icase_flags);
-       else
-               fnm = p_fnmatch(match->pattern, path->basename, icase_flags);
+       if (match->flags & GIT_ATTR_FNMATCH_ICASE)
+               flags |= FNM_CASEFOLD;
 
-       return (fnm == FNM_NOMATCH) ? false : true;
+       if (match->flags & GIT_ATTR_FNMATCH_FULLPATH) {
+               filename = path->path;
+               flags |= FNM_PATHNAME;
+       } else {
+               filename = path->basename;
+
+               if (path->is_dir)
+                       flags |= FNM_LEADING_DIR;
+       }
+
+       return (p_fnmatch(match->pattern, filename, flags) != FNM_NOMATCH);
 }
 
 bool git_attr_rule__match(
@@ -245,7 +315,6 @@ bool git_attr_rule__match(
        return matched;
 }
 
-
 git_attr_assignment *git_attr_rule__lookup_assignment(
        git_attr_rule *rule, const char *name)
 {
@@ -344,7 +413,7 @@ void git_attr_path__free(git_attr_path *info)
 int git_attr_fnmatch__parse(
        git_attr_fnmatch *spec,
        git_pool *pool,
-       const char *source,
+       const char *context,
        const char **base)
 {
        const char *pattern, *scan;
@@ -412,21 +481,21 @@ int git_attr_fnmatch__parse(
        }
 
        if ((spec->flags & GIT_ATTR_FNMATCH_FULLPATH) != 0 &&
-               source != NULL && git_path_root(pattern) < 0)
+               context != NULL && git_path_root(pattern) < 0)
        {
-        /* use context path minus the trailing filename */
-        char *slash = strrchr(source, '/');
-        size_t sourcelen = slash ? slash - source + 1 : 0;
+               /* use context path minus the trailing filename */
+               char *slash = strrchr(context, '/');
+               size_t contextlen = slash ? slash - context + 1 : 0;
 
                /* given an unrooted fullpath match from a file inside a repo,
                 * prefix the pattern with the relative directory of the source file
                 */
                spec->pattern = git_pool_malloc(
-                       pool, (uint32_t)(sourcelen + spec->length + 1));
+                       pool, (uint32_t)(contextlen + spec->length + 1));
                if (spec->pattern) {
-                       memcpy(spec->pattern, source, sourcelen);
-                       memcpy(spec->pattern + sourcelen, pattern, spec->length);
-                       spec->length += sourcelen;
+                       memcpy(spec->pattern, context, contextlen);
+                       memcpy(spec->pattern + contextlen, pattern, spec->length);
+                       spec->length += contextlen;
                        spec->pattern[spec->length] = '\0';
                }
        } else {
@@ -439,6 +508,7 @@ int git_attr_fnmatch__parse(
        } else {
                /* strip '\' that might have be used for internal whitespace */
                spec->length = git__unescape(spec->pattern);
+               /* TODO: convert remaining '\' into '/' for POSIX ??? */
        }
 
        return 0;
index dbd6696c9beedf1b75d9ee5e98cca54b7b2fcc4b..f92ce3c965c02a135a861de413abfdda806f8672 100644 (file)
@@ -13,6 +13,7 @@
 #include "pool.h"
 #include "buffer.h"
 #include "fileops.h"
+#include "attrcache.h"
 
 #define GIT_ATTR_FILE                  ".gitattributes"
 #define GIT_ATTR_FILE_INREPO   "info/attributes"
@@ -45,10 +46,10 @@ typedef struct {
        unsigned int flags;
 } git_attr_fnmatch;
 
-typedef struct {
+struct git_attr_rule {
        git_attr_fnmatch match;
        git_vector assigns;             /* vector of <git_attr_assignment*> */
-} git_attr_rule;
+};
 
 typedef struct {
        git_refcount unused;
@@ -63,17 +64,17 @@ typedef struct {
        const char *value;
 } git_attr_assignment;
 
-typedef struct {
+struct git_attr_file {
        git_refcount rc;
-       char *key;                              /* cache "source#path" this was loaded from */
-       git_vector rules;               /* vector of <rule*> or <fnmatch*> */
-       git_pool *pool;
-       bool pool_is_allocated;
+       git_attr_cache_entry *ce;
+       git_attr_cache_source source;
+       git_vector rules;                       /* vector of <rule*> or <fnmatch*> */
+       git_pool pool;
        union {
                git_oid oid;
                git_futils_filestamp stamp;
        } cache_data;
-} git_attr_file;
+};
 
 typedef struct {
        git_buf  full;
@@ -82,29 +83,41 @@ typedef struct {
        int      is_dir;
 } git_attr_path;
 
-typedef enum {
-       GIT_ATTR_FILE_FROM_FILE = 0,
-       GIT_ATTR_FILE_FROM_INDEX = 1
-} git_attr_file_source;
-
 /*
  * git_attr_file API
  */
 
-extern int git_attr_file__new(
-       git_attr_file **attrs_ptr, git_attr_file_source src, const char *path, git_pool *pool);
+int git_attr_file__new(
+       git_attr_file **out,
+       git_attr_cache_entry *ce,
+       git_attr_cache_source source);
+
+void git_attr_file__free(git_attr_file *file);
+
+int git_attr_file__load(
+       git_attr_file **out,
+       git_repository *repo,
+       git_attr_cache_entry *ce,
+       git_attr_cache_source source,
+       git_attr_cache_parser parser,
+       void *payload);
 
-extern int git_attr_file__new_and_load(
-       git_attr_file **attrs_ptr, const char *path);
+int git_attr_file__load_standalone(
+       git_attr_file **out,
+       const char *path);
 
-extern void git_attr_file__free(git_attr_file *file);
+int git_attr_file__out_of_date(
+       git_repository *repo, git_attr_file *file);
 
-extern void git_attr_file__clear_rules(git_attr_file *file);
+int git_attr_file__parse_buffer(
+       git_repository *repo,
+       git_attr_file *attrs,
+       const char *data,
+       void *payload);
 
-extern int git_attr_file__parse_buffer(
-       git_repository *repo, void *parsedata, const char *buf, git_attr_file *file);
+void git_attr_file__clear_rules(git_attr_file *file);
 
-extern int git_attr_file__lookup_one(
+int git_attr_file__lookup_one(
        git_attr_file *file,
        const git_attr_path *path,
        const char *attr,
@@ -115,7 +128,7 @@ extern int git_attr_file__lookup_one(
        git_vector_rforeach(&(file)->rules, (iter), (rule)) \
                if (git_attr_rule__match((rule), (path)))
 
-extern uint32_t git_attr_file__name_hash(const char *name);
+uint32_t git_attr_file__name_hash(const char *name);
 
 
 /*
diff --git a/src/attrcache.c b/src/attrcache.c
new file mode 100644 (file)
index 0000000..6d09723
--- /dev/null
@@ -0,0 +1,397 @@
+#include "common.h"
+#include "repository.h"
+#include "attr_file.h"
+#include "config.h"
+#include "sysdir.h"
+#include "ignore.h"
+
+GIT__USE_STRMAP;
+
+GIT_INLINE(int) attr_cache_lock(git_attr_cache *cache)
+{
+       GIT_UNUSED(cache); /* avoid warning if threading is off */
+
+       if (git_mutex_lock(&cache->lock) < 0) {
+               giterr_set(GITERR_OS, "Unable to get attr cache lock");
+               return -1;
+       }
+       return 0;
+}
+
+GIT_INLINE(void) attr_cache_unlock(git_attr_cache *cache)
+{
+       GIT_UNUSED(cache); /* avoid warning if threading is off */
+       git_mutex_unlock(&cache->lock);
+}
+
+GIT_INLINE(git_attr_cache_entry *) attr_cache_lookup_entry(
+       git_attr_cache *cache, const char *path)
+{
+       khiter_t pos = git_strmap_lookup_index(cache->files, path);
+
+       if (git_strmap_valid_index(cache->files, pos))
+               return git_strmap_value_at(cache->files, pos);
+       else
+               return NULL;
+}
+
+int git_attr_cache_entry__new(
+       git_attr_cache_entry **out,
+       const char *base,
+       const char *path,
+       git_pool *pool)
+{
+       size_t baselen = base ? strlen(base) : 0, pathlen = strlen(path);
+       size_t cachesize = sizeof(git_attr_cache_entry) + baselen + pathlen + 1;
+       git_attr_cache_entry *ce;
+
+       ce = git_pool_mallocz(pool, cachesize);
+       GITERR_CHECK_ALLOC(ce);
+
+       if (baselen)
+               memcpy(ce->fullpath, base, baselen);
+       memcpy(&ce->fullpath[baselen], path, pathlen);
+       ce->path = &ce->fullpath[baselen];
+       *out = ce;
+
+       return 0;
+}
+
+/* call with attrcache locked */
+static int attr_cache_make_entry(
+       git_attr_cache_entry **out, git_repository *repo, const char *path)
+{
+       int error = 0;
+       git_attr_cache *cache = git_repository_attr_cache(repo);
+       git_attr_cache_entry *ce = NULL;
+
+       error = git_attr_cache_entry__new(
+               &ce, git_repository_workdir(repo), path, &cache->pool);
+
+       if (!error) {
+               git_strmap_insert(cache->files, ce->path, ce, error);
+               if (error > 0)
+                       error = 0;
+       }
+
+       *out = ce;
+       return error;
+}
+
+/* insert entry or replace existing if we raced with another thread */
+static int attr_cache_upsert(git_attr_cache *cache, git_attr_file *file)
+{
+       git_attr_cache_entry *ce;
+       git_attr_file *old;
+
+       if (attr_cache_lock(cache) < 0)
+               return -1;
+
+       ce = attr_cache_lookup_entry(cache, file->ce->path);
+
+       old = ce->file[file->source];
+
+       GIT_REFCOUNT_OWN(file, ce);
+       GIT_REFCOUNT_INC(file);
+       ce->file[file->source] = file;
+
+       if (old) {
+               GIT_REFCOUNT_OWN(old, NULL);
+               git_attr_file__free(old);
+       }
+
+       attr_cache_unlock(cache);
+       return 0;
+}
+
+static int attr_cache_remove(git_attr_cache *cache, git_attr_file *file)
+{
+       int error = 0;
+       git_attr_cache_entry *ce;
+       bool found = false;
+
+       if (!file)
+               return 0;
+       if ((error = attr_cache_lock(cache)) < 0)
+               return error;
+
+       if ((ce = attr_cache_lookup_entry(cache, file->ce->path)) != NULL &&
+               ce->file[file->source] == file)
+       {
+               ce->file[file->source] = NULL;
+               found = true;
+       }
+
+       attr_cache_unlock(cache);
+
+       if (found)
+               git_attr_file__free(file);
+
+       return error;
+}
+
+int git_attr_cache__get(
+       git_attr_file **out,
+       git_repository *repo,
+       git_attr_cache_source source,
+       const char *base,
+       const char *filename,
+       git_attr_cache_parser parser,
+       void *payload)
+{
+       int error = 0;
+       git_buf path = GIT_BUF_INIT;
+       const char *wd = git_repository_workdir(repo), *relfile;
+       git_attr_cache *cache = git_repository_attr_cache(repo);
+       git_attr_cache_entry *ce = NULL;
+       git_attr_file *file = NULL;
+
+       /* join base and path as needed */
+       if (base != NULL && git_path_root(filename) < 0) {
+               if (git_buf_joinpath(&path, base, filename) < 0)
+                       return -1;
+               filename = path.ptr;
+       }
+
+       relfile = filename;
+       if (wd && !git__prefixcmp(relfile, wd))
+               relfile += strlen(wd);
+
+       /* check cache for existing entry */
+       if ((error = attr_cache_lock(cache)) < 0)
+               goto cleanup;
+
+       ce = attr_cache_lookup_entry(cache, relfile);
+       if (!ce) {
+               if ((error = attr_cache_make_entry(&ce, repo, relfile)) < 0)
+                       goto cleanup;
+       } else if (ce->file[source] != NULL) {
+               file = ce->file[source];
+               GIT_REFCOUNT_INC(file);
+       }
+
+       attr_cache_unlock(cache);
+
+       /* if this is not a file backed entry, just create a new empty one */
+       if (!parser) {
+               error = git_attr_file__new(&file, ce, source);
+               goto cleanup;
+       }
+
+       /* otherwise load and/or reload as needed */
+       switch (git_attr_file__out_of_date(repo, file)) {
+       case 1:
+               if (!(error = git_attr_file__load(
+                                 &file, repo, ce, source, parser, payload)))
+                       error = attr_cache_upsert(cache, file);
+               break;
+       case 0:
+               /* just use the file */
+               break;
+       case GIT_ENOTFOUND:
+               /* did exist and now does not - remove from cache */
+               error = attr_cache_remove(cache, file);
+               file = NULL;
+               break;
+       default:
+               /* other error (e.g. out of memory, can't read index) */
+               giterr_clear();
+               break;
+       }
+
+cleanup:
+       *out = error ? NULL : file;
+       git_buf_free(&path);
+       return error;
+}
+
+bool git_attr_cache__is_cached(
+       git_repository *repo,
+       git_attr_cache_source source,
+       const char *filename)
+{
+       git_attr_cache *cache = git_repository_attr_cache(repo);
+       git_strmap *files;
+       khiter_t pos;
+       git_attr_cache_entry *ce;
+
+       if (!(cache = git_repository_attr_cache(repo)) ||
+               !(files = cache->files))
+               return false;
+
+       pos = git_strmap_lookup_index(files, filename);
+       if (!git_strmap_valid_index(files, pos))
+               return false;
+
+       ce = git_strmap_value_at(files, pos);
+
+       return ce && (ce->file[source] != NULL);
+}
+
+
+static int attr_cache__lookup_path(
+       char **out, git_config *cfg, const char *key, const char *fallback)
+{
+       git_buf buf = GIT_BUF_INIT;
+       int error;
+       const git_config_entry *entry = NULL;
+
+       *out = NULL;
+
+       if ((error = git_config__lookup_entry(&entry, cfg, key, false)) < 0)
+               return error;
+
+       if (entry) {
+               const char *cfgval = entry->value;
+
+               /* expand leading ~/ as needed */
+               if (cfgval && cfgval[0] == '~' && cfgval[1] == '/' &&
+                       !git_sysdir_find_global_file(&buf, &cfgval[2]))
+                       *out = git_buf_detach(&buf);
+               else if (cfgval)
+                       *out = git__strdup(cfgval);
+
+       }
+       else if (!git_sysdir_find_xdg_file(&buf, fallback))
+               *out = git_buf_detach(&buf);
+
+       git_buf_free(&buf);
+
+       return error;
+}
+
+static void attr_cache__free(git_attr_cache *cache)
+{
+       if (!cache)
+               return;
+
+       if (cache->files != NULL) {
+               git_attr_file *file;
+
+               git_strmap_foreach_value(cache->files, file, {
+                       git_attr_file__free(file);
+               });
+               git_strmap_free(cache->files);
+       }
+
+       if (cache->macros != NULL) {
+               git_attr_rule *rule;
+
+               git_strmap_foreach_value(cache->macros, rule, {
+                       git_attr_rule__free(rule);
+               });
+               git_strmap_free(cache->macros);
+       }
+
+       git_pool_clear(&cache->pool);
+
+       git__free(cache->cfg_attr_file);
+       cache->cfg_attr_file = NULL;
+
+       git__free(cache->cfg_excl_file);
+       cache->cfg_excl_file = NULL;
+
+       git_mutex_free(&cache->lock);
+
+       git__free(cache);
+}
+
+int git_attr_cache__init(git_repository *repo)
+{
+       int ret = 0;
+       git_attr_cache *cache = git_repository_attr_cache(repo);
+       git_config *cfg;
+
+       if (cache)
+               return 0;
+
+       if ((ret = git_repository_config__weakptr(&cfg, repo)) < 0)
+               return ret;
+
+       cache = git__calloc(1, sizeof(git_attr_cache));
+       GITERR_CHECK_ALLOC(cache);
+
+       /* set up lock */
+       if (git_mutex_init(&cache->lock) < 0) {
+               giterr_set(GITERR_OS, "Unable to initialize lock for attr cache");
+               git__free(cache);
+               return -1;
+       }
+
+       /* cache config settings for attributes and ignores */
+       ret = attr_cache__lookup_path(
+               &cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG, GIT_ATTR_FILE_XDG);
+       if (ret < 0)
+               goto cancel;
+
+       ret = attr_cache__lookup_path(
+               &cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG, GIT_IGNORE_FILE_XDG);
+       if (ret < 0)
+               goto cancel;
+
+       /* allocate hashtable for attribute and ignore file contents,
+        * hashtable for attribute macros, and string pool
+        */
+       if ((ret = git_strmap_alloc(&cache->files)) < 0 ||
+               (ret = git_strmap_alloc(&cache->macros)) < 0 ||
+               (ret = git_pool_init(&cache->pool, 1, 0)) < 0)
+               goto cancel;
+
+       cache = git__compare_and_swap(&repo->attrcache, NULL, cache);
+       if (cache)
+               goto cancel; /* raced with another thread, free this but no error */
+
+       /* insert default macros */
+       return git_attr_add_macro(repo, "binary", "-diff -crlf -text");
+
+cancel:
+       attr_cache__free(cache);
+       return ret;
+}
+
+void git_attr_cache_flush(git_repository *repo)
+{
+       git_attr_cache *cache;
+
+       /* this could be done less expensively, but for now, we'll just free
+        * the entire attrcache and let the next use reinitialize it...
+        */
+       if (repo && (cache = git__swap(repo->attrcache, NULL)) != NULL)
+               attr_cache__free(cache);
+}
+
+int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro)
+{
+       git_attr_cache *cache = git_repository_attr_cache(repo);
+       git_strmap *macros = cache->macros;
+       int error;
+
+       /* TODO: generate warning log if (macro->assigns.length == 0) */
+       if (macro->assigns.length == 0)
+               return 0;
+
+       if (git_mutex_lock(&cache->lock) < 0) {
+               giterr_set(GITERR_OS, "Unable to get attr cache lock");
+               error = -1;
+       } else {
+               git_strmap_insert(macros, macro->match.pattern, macro, error);
+               git_mutex_unlock(&cache->lock);
+       }
+
+       return (error < 0) ? -1 : 0;
+}
+
+git_attr_rule *git_attr_cache__lookup_macro(
+       git_repository *repo, const char *name)
+{
+       git_strmap *macros = git_repository_attr_cache(repo)->macros;
+       khiter_t pos;
+
+       pos = git_strmap_lookup_index(macros, name);
+
+       if (!git_strmap_valid_index(macros, pos))
+               return NULL;
+
+       return (git_attr_rule *)git_strmap_value_at(macros, pos);
+}
+
index 4f9cff6bbf8b0ef66931730f6482e595ad7018a0..8e7f022b08dfa1b69ef8fc555d3c121432e58da5 100644 (file)
@@ -9,11 +9,15 @@
 
 #include "pool.h"
 #include "strmap.h"
+#include "buffer.h"
+
+#define GIT_ATTR_CONFIG       "core.attributesfile"
+#define GIT_IGNORE_CONFIG     "core.excludesfile"
 
 typedef struct {
        char *cfg_attr_file; /* cached value of core.attributesfile */
        char *cfg_excl_file; /* cached value of core.excludesfile */
-       git_strmap *files;       /* hash path to git_attr_file of rules */
+       git_strmap *files;       /* hash path to git_attr_cache_entry records */
        git_strmap *macros;      /* hash name to vector<git_attr_assignment> */
        git_mutex lock;
        git_pool  pool;
@@ -21,4 +25,53 @@ typedef struct {
 
 extern int git_attr_cache__init(git_repository *repo);
 
+typedef enum {
+       GIT_ATTR_CACHE__FROM_FILE = 0,
+       GIT_ATTR_CACHE__FROM_INDEX = 1,
+
+       GIT_ATTR_CACHE_NUM_SOURCES = 2
+} git_attr_cache_source;
+
+typedef struct git_attr_file git_attr_file;
+typedef struct git_attr_rule git_attr_rule;
+
+typedef struct {
+       git_attr_file *file[GIT_ATTR_CACHE_NUM_SOURCES];
+       const char *path; /* points into fullpath */
+       char fullpath[GIT_FLEX_ARRAY];
+} git_attr_cache_entry;
+
+typedef int (*git_attr_cache_parser)(
+       git_repository *repo,
+       git_attr_file *file,
+       const char *data,
+       void *payload);
+
+/* get file - loading and reload as needed */
+extern int git_attr_cache__get(
+       git_attr_file **file,
+       git_repository *repo,
+       git_attr_cache_source source,
+       const char *base,
+       const char *filename,
+       git_attr_cache_parser parser,
+       void *payload);
+
+extern bool git_attr_cache__is_cached(
+       git_repository *repo,
+       git_attr_cache_source source,
+       const char *path);
+
+extern int git_attr_cache__insert_macro(
+       git_repository *repo, git_attr_rule *macro);
+
+extern git_attr_rule *git_attr_cache__lookup_macro(
+       git_repository *repo, const char *name);
+
+extern int git_attr_cache_entry__new(
+       git_attr_cache_entry **out,
+       const char *base,
+       const char *path,
+       git_pool *pool);
+
 #endif
index 5709499b0c0fa3c89b98c82c017428409b08e638..d8d819151629f49bf7211ff803c3e7f96af247af 100644 (file)
@@ -804,10 +804,8 @@ int git_futils_filestamp_check(
        if (stamp == NULL)
                return 1;
 
-       if (p_stat(path, &st) < 0) {
-               giterr_set(GITERR_OS, "Could not stat '%s'", path);
+       if (p_stat(path, &st) < 0)
                return GIT_ENOTFOUND;
-       }
 
        if (stamp->mtime == (git_time_t)st.st_mtime &&
                stamp->size  == (git_off_t)st.st_size   &&
index 6a65235dee3617aa973e74093adfe6a68a306ea4..cfc2ce701e89a652fb0c51223d7a1b934c37df2b 100644 (file)
@@ -292,13 +292,14 @@ typedef struct {
  * Compare stat information for file with reference info.
  *
  * This function updates the file stamp to current data for the given path
- * and returns 0 if the file is up-to-date relative to the prior setting or
- * 1 if the file has been changed.  (This also may return GIT_ENOTFOUND if
- * the file doesn't exist.)
+ * and returns 0 if the file is up-to-date relative to the prior setting,
+ * 1 if the file has been changed, or GIT_ENOTFOUND if the file doesn't
+ * exist.  This will not call giterr_set, so you must set the error if you
+ * plan to return an error.
  *
  * @param stamp File stamp to be checked
  * @param path Path to stat and check if changed
- * @return 0 if up-to-date, 1 if out-of-date, <0 on error
+ * @return 0 if up-to-date, 1 if out-of-date, GIT_ENOTFOUND if cannot stat
  */
 extern int git_futils_filestamp_check(
        git_futils_filestamp *stamp, const char *path);
index 0fb042a3498464db6db6d3bbd0102abb60b7abfb..3ee7ba03a5890cde5c7acdce702c38402577b276 100644 (file)
@@ -1,7 +1,7 @@
 #include "git2/ignore.h"
 #include "common.h"
 #include "ignore.h"
-#include "attr.h"
+#include "attr_file.h"
 #include "path.h"
 #include "config.h"
 
 #define GIT_IGNORE_DEFAULT_RULES ".\n..\n.git\n"
 
 static int parse_ignore_file(
-       git_repository *repo, void *parsedata, const char *buffer, git_attr_file *ignores)
+       git_repository *repo,
+       git_attr_file *attrs,
+       const char *data,
+       void *payload)
 {
        int error = 0;
-       git_attr_fnmatch *match = NULL;
-       const char *scan = NULL, *context = NULL;
        int ignore_case = false;
+       const char *scan = data, *context = NULL;
+       git_attr_fnmatch *match = NULL;
 
-       /* Prefer to have the caller pass in a git_ignores as the parsedata
-        * object.  If they did not, then look up the value of ignore_case */
-       if (parsedata != NULL)
-               ignore_case = ((git_ignores *)parsedata)->ignore_case;
+       /* either read ignore_case from ignores structure or use repo config */
+       if (payload != NULL)
+               ignore_case = ((git_ignores *)payload)->ignore_case;
        else if (git_repository__cvar(&ignore_case, repo, GIT_CVAR_IGNORECASE) < 0)
-               return error;
-
-       if (ignores->key &&
-               git_path_root(ignores->key + 2) < 0 &&
-               git__suffixcmp(ignores->key, "/" GIT_IGNORE_FILE) == 0)
-               context = ignores->key + 2;
+               giterr_clear();
 
-       scan = buffer;
+       /* if subdir file path, convert context for file paths */
+       if (attrs->ce &&
+               git_path_root(attrs->ce->path) < 0 &&
+               !git__suffixcmp(attrs->ce->path, "/" GIT_IGNORE_FILE))
+               context = attrs->ce->path;
 
        while (!error && *scan) {
                if (!match) {
@@ -40,7 +41,7 @@ static int parse_ignore_file(
                match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG;
 
                if (!(error = git_attr_fnmatch__parse(
-                       match, ignores->pool, context, &scan)))
+                       match, &attrs->pool, context, &scan)))
                {
                        match->flags |= GIT_ATTR_FNMATCH_IGNORE;
 
@@ -48,7 +49,7 @@ static int parse_ignore_file(
                                match->flags |= GIT_ATTR_FNMATCH_ICASE;
 
                        scan = git__next_line(scan);
-                       error = git_vector_insert(&ignores->rules, match);
+                       error = git_vector_insert(&attrs->rules, match);
                }
 
                if (error != 0) {
@@ -67,28 +68,46 @@ static int parse_ignore_file(
        return error;
 }
 
-#define push_ignore_file(R,IGN,S,B,F) \
-       git_attr_cache__push_file((R),(B),(F),GIT_ATTR_FILE_FROM_FILE,parse_ignore_file,(IGN),(S))
+static int push_ignore_file(
+       git_ignores *ignores,
+       git_vector *which_list,
+       const char *base,
+       const char *filename)
+{
+       int error = 0;
+       git_attr_file *file = NULL;
+
+       if ((error = git_attr_cache__get(
+                       &file, ignores->repo, GIT_ATTR_CACHE__FROM_FILE,
+                       base, filename, parse_ignore_file, ignores)) < 0 ||
+               (error = git_vector_insert(which_list, file)) < 0)
+               git_attr_file__free(file);
+
+       return error;
+}
 
 static int push_one_ignore(void *payload, git_buf *path)
 {
        git_ignores *ign = payload;
-
        ign->depth++;
-
-       return push_ignore_file(
-               ign->repo, ign, &ign->ign_path, path->ptr, GIT_IGNORE_FILE);
+       return push_ignore_file(ign, &ign->ign_path, path->ptr, GIT_IGNORE_FILE);
 }
 
-static int get_internal_ignores(git_attr_file **ign, git_repository *repo)
+static int get_internal_ignores(git_attr_file **out, git_repository *repo)
 {
        int error;
 
-       if (!(error = git_attr_cache__init(repo)))
-               error = git_attr_cache__internal_file(repo, GIT_IGNORE_INTERNAL, ign);
+       if ((error = git_attr_cache__init(repo)) < 0)
+               return error;
+
+       /* get with NULL parser, gives existing or empty git_attr_file */
+       error = git_attr_cache__get(
+               out, repo, GIT_ATTR_CACHE__FROM_FILE,
+               NULL, GIT_IGNORE_INTERNAL, NULL, NULL);
 
-       if (!error && !(*ign)->rules.length)
-               error = parse_ignore_file(repo, NULL, GIT_IGNORE_DEFAULT_RULES, *ign);
+       /* if internal rules list is empty, insert default rules */
+       if (!error && !(*out)->rules.length)
+               error = parse_ignore_file(repo, *out, GIT_IGNORE_DEFAULT_RULES, NULL);
 
        return error;
 }
@@ -127,8 +146,7 @@ int git_ignore__for_path(
                goto cleanup;
 
        /* set up internals */
-       error = get_internal_ignores(&ignores->ign_internal, repo);
-       if (error < 0)
+       if ((error = get_internal_ignores(&ignores->ign_internal, repo)) < 0)
                goto cleanup;
 
        /* load .gitignore up the path */
@@ -140,14 +158,16 @@ int git_ignore__for_path(
        }
 
        /* load .git/info/exclude */
-       error = push_ignore_file(repo, ignores, &ignores->ign_global,
+       error = push_ignore_file(
+               ignores, &ignores->ign_global,
                git_repository_path(repo), GIT_IGNORE_FILE_INREPO);
        if (error < 0)
                goto cleanup;
 
        /* load core.excludesfile */
        if (git_repository_attr_cache(repo)->cfg_excl_file != NULL)
-               error = push_ignore_file(repo, ignores, &ignores->ign_global, NULL,
+               error = push_ignore_file(
+                       ignores, &ignores->ign_global, NULL,
                        git_repository_attr_cache(repo)->cfg_excl_file);
 
 cleanup:
@@ -165,35 +185,33 @@ int git_ignore__push_dir(git_ignores *ign, const char *dir)
        ign->depth++;
 
        return push_ignore_file(
-               ign->repo, ign, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE);
+               ign, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE);
 }
 
 int git_ignore__pop_dir(git_ignores *ign)
 {
        if (ign->ign_path.length > 0) {
                git_attr_file *file = git_vector_last(&ign->ign_path);
-               const char *start, *end, *scan;
-               size_t keylen;
+               const char *start = file->ce->path, *end;
 
-               /* - ign->dir looks something like "a/b/" (or "a/b/c/d/")
-                * - file->key looks something like "0#a/b/.gitignore
+               /* - ign->dir looks something like "/home/user/a/b/" (or "a/b/c/d/")
+                * - file->path looks something like "a/b/.gitignore
                 *
-                * We are popping the last directory off ign->dir.  We also want to
-                * remove the file from the vector if the directory part of the key
-                * matches the ign->dir path.  We need to test if the "a/b" part of
+                * We are popping the last directory off ign->dir.  We also want
+                * to remove the file from the vector if the popped directory
+                * matches the ignore path.  We need to test if the "a/b" part of
                 * the file key matches the path we are about to pop.
                 */
 
-               for (start = end = scan = &file->key[2]; *scan; ++scan)
-                       if (*scan == '/')
-                               end = scan; /* point 'end' to last '/' in key */
-               keylen = (end - start) + 1;
+               if ((end = strrchr(start, '/')) != NULL) {
+                       size_t dirlen = (end - start) + 1;
 
-               if (ign->dir.size >= keylen &&
-                       !memcmp(ign->dir.ptr + ign->dir.size - keylen, start, keylen))
-               {
-                       git_attr_file__free(git_vector_last(&ign->ign_path));
-                       git_vector_pop(&ign->ign_path);
+                       if (ign->dir.size >= dirlen &&
+                               !memcmp(ign->dir.ptr + ign->dir.size - dirlen, start, dirlen))
+                       {
+                               git_vector_pop(&ign->ign_path);
+                               git_attr_file__free(file);
+                       }
                }
        }
 
@@ -210,7 +228,7 @@ void git_ignore__free(git_ignores *ignores)
        unsigned int i;
        git_attr_file *file;
 
-       /* don't need to free ignores->ign_internal it is cached exactly once */
+       git_attr_file__free(ignores->ign_internal);
 
        git_vector_foreach(&ignores->ign_path, i, file) {
                git_attr_file__free(file);
@@ -283,10 +301,12 @@ int git_ignore_add_rule(
        const char *rules)
 {
        int error;
-       git_attr_file *ign_internal;
+       git_attr_file *ign_internal = NULL;
 
-       if (!(error = get_internal_ignores(&ign_internal, repo)))
+       if (!(error = get_internal_ignores(&ign_internal, repo))) {
                error = parse_ignore_file(repo, NULL, rules, ign_internal);
+               git_attr_file__free(ign_internal);
+       }
 
        return error;
 }
@@ -300,8 +320,10 @@ int git_ignore_clear_internal_rules(
        if (!(error = get_internal_ignores(&ign_internal, repo))) {
                git_attr_file__clear_rules(ign_internal);
 
-               return parse_ignore_file(
+               error = parse_ignore_file(
                        repo, NULL, GIT_IGNORE_DEFAULT_RULES, ign_internal);
+
+               git_attr_file__free(ign_internal);
        }
 
        return error;
index 27a557cfb5720275f85c59bc8e7063ce4bb4e47e..d7d937f63d12221d2b086a442d701e05cd10350d 100644 (file)
@@ -607,8 +607,15 @@ int git_index_read(git_index *index, int force)
        }
 
        updated = git_futils_filestamp_check(&stamp, index->index_file_path);
-       if (updated < 0 || (!updated && !force))
+       if (updated < 0) {
+               giterr_set(
+                       GITERR_INDEX,
+                       "Failed to read index: '%s' no longer exists",
+                       index->index_file_path);
                return updated;
+       }
+       if (!updated && !force)
+               return 0;
 
        error = git_futils_readbuffer(&buffer, index->index_file_path);
        if (error < 0)
@@ -667,11 +674,11 @@ int git_index_write(git_index *index)
        if ((error = git_filebuf_commit(&file)) < 0)
                return error;
 
-       error = git_futils_filestamp_check(&index->stamp, index->index_file_path);
-       if (error < 0)
-               return error;
+       if (git_futils_filestamp_check(&index->stamp, index->index_file_path) < 0)
+               /* index could not be read from disk! */;
+       else
+               index->on_disk = 1;
 
-       index->on_disk = 1;
        return 0;
 }
 
index 625322034acc55bf0edd196b445e8dda602ce941..c6b22615368e082c07fc9b5e0250613b346f1ffe 100644 (file)
@@ -232,9 +232,8 @@ unlock:
 
 void git_sortedcache_updated(git_sortedcache *sc)
 {
-        /* update filestamp to latest value */
-       if (git_futils_filestamp_check(&sc->stamp, sc->path) < 0)
-               giterr_clear();
+       /* update filestamp to latest value */
+       git_futils_filestamp_check(&sc->stamp, sc->path);
 }
 
 /* release all items in sorted cache */
index 95d3d0d9c0d1c63afa4638adf396bbdbe176f210..5ddbfe828e45947adb6be42a055d398bbf229a12 100644 (file)
@@ -1693,8 +1693,6 @@ static int submodule_cache_refresh(git_submodule_cache *cache, int refresh)
                update_gitmod = (wd != NULL) ?
                        git_futils_filestamp_check(&cache->gitmodules_stamp, path.ptr) :
                        (cache->gitmodules_stamp.mtime != 0);
-               if (update_gitmod < 0)
-                       giterr_clear();
        }
 
        /* clear submodule flags that are to be refreshed */
index 4eb1d22fedffe4eb3bc2aec19669e8edf0feaa71..e35957b51423db22e4dcbb97ad07ff2a73f79093 100644 (file)
@@ -11,9 +11,9 @@ void test_attr_file__simple_read(void)
        git_attr_assignment *assign;
        git_attr_rule *rule;
 
-       cl_git_pass(git_attr_file__new_and_load(&file, cl_fixture("attr/attr0")));
+       cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr0")));
 
-       cl_assert_equal_s(cl_fixture("attr/attr0"), file->key + 2);
+       cl_assert_equal_s(cl_fixture("attr/attr0"), file->ce->path);
        cl_assert(file->rules.length == 1);
 
        rule = get_rule(0);
@@ -37,9 +37,9 @@ void test_attr_file__match_variants(void)
        git_attr_rule *rule;
        git_attr_assignment *assign;
 
-       cl_git_pass(git_attr_file__new_and_load(&file, cl_fixture("attr/attr1")));
+       cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr1")));
 
-       cl_assert_equal_s(cl_fixture("attr/attr1"), file->key + 2);
+       cl_assert_equal_s(cl_fixture("attr/attr1"), file->ce->path);
        cl_assert(file->rules.length == 10);
 
        /* let's do a thorough check of this rule, then just verify
@@ -121,9 +121,9 @@ void test_attr_file__assign_variants(void)
        git_attr_rule *rule;
        git_attr_assignment *assign;
 
-       cl_git_pass(git_attr_file__new_and_load(&file, cl_fixture("attr/attr2")));
+       cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr2")));
 
-       cl_assert_equal_s(cl_fixture("attr/attr2"), file->key + 2);
+       cl_assert_equal_s(cl_fixture("attr/attr2"), file->ce->path);
        cl_assert(file->rules.length == 11);
 
        check_one_assign(file, 0, 0, "pat0", "simple", EXPECT_TRUE, NULL);
@@ -187,8 +187,8 @@ void test_attr_file__check_attr_examples(void)
        git_attr_rule *rule;
        git_attr_assignment *assign;
 
-       cl_git_pass(git_attr_file__new_and_load(&file, cl_fixture("attr/attr3")));
-       cl_assert_equal_s(cl_fixture("attr/attr3"), file->key + 2);
+       cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr3")));
+       cl_assert_equal_s(cl_fixture("attr/attr3"), file->ce->path);
        cl_assert(file->rules.length == 3);
 
        rule = get_rule(0);
index 200bdd2c787720c307cca369585494146cea96d7..099597efcdbb97ed6f170b8537031903c0ff1559 100644 (file)
@@ -9,8 +9,8 @@ void test_attr_lookup__simple(void)
        git_attr_path path;
        const char *value = NULL;
 
-       cl_git_pass(git_attr_file__new_and_load(&file, cl_fixture("attr/attr0")));
-       cl_assert_equal_s(cl_fixture("attr/attr0"), file->key + 2);
+       cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr0")));
+       cl_assert_equal_s(cl_fixture("attr/attr0"), file->ce->path);
        cl_assert(file->rules.length == 1);
 
        cl_git_pass(git_attr_path__init(&path, "test", NULL));
@@ -129,8 +129,8 @@ void test_attr_lookup__match_variants(void)
                { NULL, NULL, 0, NULL }
        };
 
-       cl_git_pass(git_attr_file__new_and_load(&file, cl_fixture("attr/attr1")));
-       cl_assert_equal_s(cl_fixture("attr/attr1"), file->key + 2);
+       cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr1")));
+       cl_assert_equal_s(cl_fixture("attr/attr1"), file->ce->path);
        cl_assert(file->rules.length == 10);
 
        cl_git_pass(git_attr_path__init(&path, "/testing/for/pat0", NULL));
@@ -190,7 +190,7 @@ void test_attr_lookup__assign_variants(void)
                { NULL, NULL, 0, NULL }
        };
 
-       cl_git_pass(git_attr_file__new_and_load(&file, cl_fixture("attr/attr2")));
+       cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr2")));
        cl_assert(file->rules.length == 11);
 
        run_test_cases(file, cases, 0);
@@ -225,7 +225,7 @@ void test_attr_lookup__check_attr_examples(void)
                { NULL, NULL, 0, NULL }
        };
 
-       cl_git_pass(git_attr_file__new_and_load(&file, cl_fixture("attr/attr3")));
+       cl_git_pass(git_attr_file__load_standalone(&file, cl_fixture("attr/attr3")));
        cl_assert(file->rules.length == 3);
 
        run_test_cases(file, cases, 0);
@@ -250,9 +250,9 @@ void test_attr_lookup__from_buffer(void)
                { NULL, NULL, 0, NULL }
        };
 
-       cl_git_pass(git_attr_file__new(&file, 0, NULL, NULL));
+       cl_git_pass(git_attr_file__new(&file, NULL, 0));
 
-       cl_git_pass(git_attr_file__parse_buffer(NULL, NULL, "a* foo\nabc bar\n* baz", file));
+       cl_git_pass(git_attr_file__parse_buffer(NULL, file, "a* foo\nabc bar\n* baz", NULL));
 
        cl_assert(file->rules.length == 3);
 
index 5565c4bf1b74e867f8c0146ed665be8a14237344..562eec71c0fcbfeb647349b7e85162c47c0f30d3 100644 (file)
@@ -1,59 +1,22 @@
 #include "clar_libgit2.h"
-#include "thread-utils.h"
+#include "thread_helpers.h"
 
 static git_repository *_repo;
 static git_tree *_a, *_b;
 static git_atomic _counts[4];
 static int _check_counts;
 
+#define THREADS 20
+
 void test_threads_diff__cleanup(void)
 {
        cl_git_sandbox_cleanup();
 }
 
-static void run_in_parallel(
-       int repeats, int threads, void *(*func)(void *),
-       void (*before_test)(void), void (*after_test)(void))
-{
-       int r, t, *id = git__calloc(threads, sizeof(int));
-#ifdef GIT_THREADS
-       git_thread *th = git__calloc(threads, sizeof(git_thread));
-       cl_assert(th != NULL);
-#else
-       void *th = NULL;
-#endif
-
-       cl_assert(id != NULL);
-
-       for (r = 0; r < repeats; ++r) {
-               _repo = cl_git_sandbox_reopen(); /* reopen sandbox to flush caches */
-
-               if (before_test) before_test();
-
-               for (t = 0; t < threads; ++t) {
-                       id[t] = t;
-#ifdef GIT_THREADS
-                       cl_git_pass(git_thread_create(&th[t], NULL, func, &id[t]));
-#else
-                       cl_assert(func(&id[t]) == &id[t]);
-#endif
-               }
-
-#ifdef GIT_THREADS
-               for (t = 0; t < threads; ++t)
-                       cl_git_pass(git_thread_join(th[t], NULL));
-               memset(th, 0, threads * sizeof(git_thread));
-#endif
-
-               if (after_test) after_test();
-       }
-
-       git__free(id);
-       git__free(th);
-}
-
 static void setup_trees(void)
 {
+       _repo = cl_git_sandbox_reopen(); /* reopen sandbox to flush caches */
+
        cl_git_pass(git_revparse_single(
                (git_object **)&_a, _repo, "0017bd4ab1^{tree}"));
        cl_git_pass(git_revparse_single(
@@ -62,8 +25,6 @@ static void setup_trees(void)
        memset(_counts, 0, sizeof(_counts));
 }
 
-#define THREADS 20
-
 static void free_trees(void)
 {
        git_tree_free(_a); _a = NULL;
diff --git a/tests/threads/iterator.c b/tests/threads/iterator.c
new file mode 100644 (file)
index 0000000..4dd251f
--- /dev/null
@@ -0,0 +1,49 @@
+#include "clar_libgit2.h"
+#include "thread_helpers.h"
+#include "iterator.h"
+
+static git_repository *_repo;
+
+void test_threads_iterator__cleanup(void)
+{
+       cl_git_sandbox_cleanup();
+}
+
+static void *run_workdir_iterator(void *arg)
+{
+       int error = 0, thread = *(int *)arg;
+       git_iterator *iter;
+       const git_index_entry *entry = NULL;
+
+       cl_git_pass(git_iterator_for_workdir(
+               &iter, _repo, GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL));
+
+       while (!error) {
+               if (entry && entry->mode == GIT_FILEMODE_TREE) {
+                       error = git_iterator_advance_into(&entry, iter);
+
+                       if (error == GIT_ENOTFOUND)
+                               error = git_iterator_advance(&entry, iter);
+               } else {
+                       error = git_iterator_advance(&entry, iter);
+               }
+
+               if (!error)
+                       (void)git_iterator_current_is_ignored(iter);
+       }
+
+       cl_assert_equal_i(GIT_ITEROVER, error);
+
+       git_iterator_free(iter);
+
+       return arg;
+}
+
+
+void test_threads_iterator__workdir(void)
+{
+       _repo = cl_git_sandbox_init("status");
+
+       run_in_parallel(
+               1, 20, run_workdir_iterator, NULL, NULL);
+}
diff --git a/tests/threads/thread_helpers.c b/tests/threads/thread_helpers.c
new file mode 100644 (file)
index 0000000..25370dd
--- /dev/null
@@ -0,0 +1,44 @@
+#include "clar_libgit2.h"
+#include "thread_helpers.h"
+
+void run_in_parallel(
+       int repeats,
+       int threads,
+       void *(*func)(void *),
+       void (*before_test)(void),
+       void (*after_test)(void))
+{
+       int r, t, *id = git__calloc(threads, sizeof(int));
+#ifdef GIT_THREADS
+       git_thread *th = git__calloc(threads, sizeof(git_thread));
+       cl_assert(th != NULL);
+#else
+       void *th = NULL;
+#endif
+
+       cl_assert(id != NULL);
+
+       for (r = 0; r < repeats; ++r) {
+               if (before_test) before_test();
+
+               for (t = 0; t < threads; ++t) {
+                       id[t] = t;
+#ifdef GIT_THREADS
+                       cl_git_pass(git_thread_create(&th[t], NULL, func, &id[t]));
+#else
+                       cl_assert(func(&id[t]) == &id[t]);
+#endif
+               }
+
+#ifdef GIT_THREADS
+               for (t = 0; t < threads; ++t)
+                       cl_git_pass(git_thread_join(th[t], NULL));
+               memset(th, 0, threads * sizeof(git_thread));
+#endif
+
+               if (after_test) after_test();
+       }
+
+       git__free(id);
+       git__free(th);
+}
diff --git a/tests/threads/thread_helpers.h b/tests/threads/thread_helpers.h
new file mode 100644 (file)
index 0000000..3c13cfb
--- /dev/null
@@ -0,0 +1,8 @@
+#include "thread-utils.h"
+
+void run_in_parallel(
+       int repeats,
+       int threads,
+       void *(*func)(void *),
+       void (*before_test)(void),
+       void (*after_test)(void));