diff options
author | Russell Belfer <rb@github.com> | 2014-04-10 22:31:01 -0700 |
---|---|---|
committer | Russell Belfer <rb@github.com> | 2014-04-17 14:56:41 -0700 |
commit | 7d4908724fd7d4d8e096b4faf2c652ba5b77644e (patch) | |
tree | 2fcd09e7e040607c124f6e658c31f33e1aa1868a | |
parent | 1fa17b5c92cb92a2785fba403b87525169b205c0 (diff) | |
download | libgit2-7d4908724fd7d4d8e096b4faf2c652ba5b77644e.tar.gz |
Attribute file cache refactor
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.
-rw-r--r-- | src/attr.c | 478 | ||||
-rw-r--r-- | src/attr.h | 34 | ||||
-rw-r--r-- | src/attr_file.c | 326 | ||||
-rw-r--r-- | src/attr_file.h | 59 | ||||
-rw-r--r-- | src/attrcache.c | 397 | ||||
-rw-r--r-- | src/attrcache.h | 55 | ||||
-rw-r--r-- | src/fileops.c | 4 | ||||
-rw-r--r-- | src/fileops.h | 9 | ||||
-rw-r--r-- | src/ignore.c | 128 | ||||
-rw-r--r-- | src/index.c | 17 | ||||
-rw-r--r-- | src/sortedcache.c | 5 | ||||
-rw-r--r-- | src/submodule.c | 2 | ||||
-rw-r--r-- | tests/attr/file.c | 16 | ||||
-rw-r--r-- | tests/attr/lookup.c | 16 | ||||
-rw-r--r-- | tests/threads/diff.c | 49 | ||||
-rw-r--r-- | tests/threads/iterator.c | 49 | ||||
-rw-r--r-- | tests/threads/thread_helpers.c | 44 | ||||
-rw-r--r-- | tests/threads/thread_helpers.h | 8 |
18 files changed, 940 insertions, 756 deletions
diff --git a/src/attr.c b/src/attr.c index f52a8a97b..c53a728de 100644 --- a/src/attr.c +++ b/src/attr.c @@ -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); -} - diff --git a/src/attr.h b/src/attr.h index 19c979bcd..f9f216d07 100644 --- a/src/attr.h +++ b/src/attr.h @@ -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 diff --git a/src/attr_file.c b/src/attr_file.c index 695f661a8..86b3448ee 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -1,86 +1,173 @@ #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; diff --git a/src/attr_file.h b/src/attr_file.h index dbd6696c9..f92ce3c96 100644 --- a/src/attr_file.h +++ b/src/attr_file.h @@ -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 index 000000000..6d097234c --- /dev/null +++ b/src/attrcache.c @@ -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); +} + diff --git a/src/attrcache.h b/src/attrcache.h index 4f9cff6bb..8e7f022b0 100644 --- a/src/attrcache.h +++ b/src/attrcache.h @@ -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 diff --git a/src/fileops.c b/src/fileops.c index 5709499b0..d8d819151 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -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 && diff --git a/src/fileops.h b/src/fileops.h index 6a65235de..cfc2ce701 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -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); diff --git a/src/ignore.c b/src/ignore.c index 0fb042a34..3ee7ba03a 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -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" @@ -10,26 +10,27 @@ #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; diff --git a/src/index.c b/src/index.c index 27a557cfb..d7d937f63 100644 --- a/src/index.c +++ b/src/index.c @@ -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; } diff --git a/src/sortedcache.c b/src/sortedcache.c index 625322034..c6b226153 100644 --- a/src/sortedcache.c +++ b/src/sortedcache.c @@ -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 */ diff --git a/src/submodule.c b/src/submodule.c index 95d3d0d9c..5ddbfe828 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -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 */ diff --git a/tests/attr/file.c b/tests/attr/file.c index 4eb1d22fe..e35957b51 100644 --- a/tests/attr/file.c +++ b/tests/attr/file.c @@ -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); diff --git a/tests/attr/lookup.c b/tests/attr/lookup.c index 200bdd2c7..099597efc 100644 --- a/tests/attr/lookup.c +++ b/tests/attr/lookup.c @@ -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); diff --git a/tests/threads/diff.c b/tests/threads/diff.c index 5565c4bf1..562eec71c 100644 --- a/tests/threads/diff.c +++ b/tests/threads/diff.c @@ -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 index 000000000..4dd251fa5 --- /dev/null +++ b/tests/threads/iterator.c @@ -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 index 000000000..25370dddb --- /dev/null +++ b/tests/threads/thread_helpers.c @@ -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 index 000000000..3c13cfb6b --- /dev/null +++ b/tests/threads/thread_helpers.h @@ -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)); |