diff options
Diffstat (limited to 'src/attr.c')
-rw-r--r-- | src/attr.c | 560 |
1 files changed, 407 insertions, 153 deletions
diff --git a/src/attr.c b/src/attr.c index 17571f6a8..093f64d5c 100644 --- a/src/attr.c +++ b/src/attr.c @@ -3,13 +3,21 @@ #include "config.h" #include <ctype.h> +GIT__USE_STRMAP; + static int collect_attr_files( - git_repository *repo, const char *path, git_vector *files); + git_repository *repo, + uint32_t flags, + const char *path, + git_vector *files); int git_attr_get( - git_repository *repo, const char *pathname, - const char *name, const char **value) + const char **value, + git_repository *repo, + uint32_t flags, + const char *pathname, + const char *name) { int error; git_attr_path path; @@ -21,10 +29,11 @@ int git_attr_get( *value = NULL; - if ((error = git_attr_path__init( - &path, pathname, git_repository_workdir(repo))) < GIT_SUCCESS || - (error = collect_attr_files(repo, pathname, &files)) < GIT_SUCCESS) - return git__rethrow(error, "Could not get attribute for %s", pathname); + if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0) + return -1; + + if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0) + goto cleanup; attr.name = name; attr.name_hash = git_attr_file__name_hash(name); @@ -33,18 +42,17 @@ int git_attr_get( git_attr_file__foreach_matching_rule(file, &path, j, rule) { int pos = git_vector_bsearch(&rule->assigns, &attr); - git_clearerror(); /* okay if search failed */ - if (pos >= 0) { *value = ((git_attr_assignment *)git_vector_get( &rule->assigns, pos))->value; - goto found; + goto cleanup; } } } -found: +cleanup: git_vector_free(&files); + git_attr_path__free(&path); return error; } @@ -56,8 +64,12 @@ typedef struct { } attr_get_many_info; int git_attr_get_many( - git_repository *repo, const char *pathname, - size_t num_attr, const char **names, const char **values) + const char **values, + git_repository *repo, + uint32_t flags, + const char *pathname, + size_t num_attr, + const char **names) { int error; git_attr_path path; @@ -70,15 +82,14 @@ int git_attr_get_many( memset((void *)values, 0, sizeof(const char *) * num_attr); - if ((error = git_attr_path__init( - &path, pathname, git_repository_workdir(repo))) < GIT_SUCCESS || - (error = collect_attr_files(repo, pathname, &files)) < GIT_SUCCESS) - return git__rethrow(error, "Could not get attributes for %s", pathname); + if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0) + return -1; - if ((info = git__calloc(num_attr, sizeof(attr_get_many_info))) == NULL) { - git__rethrow(GIT_ENOMEM, "Could not get attributes for %s", pathname); + if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0) goto cleanup; - } + + info = git__calloc(num_attr, sizeof(attr_get_many_info)); + GITERR_CHECK_ALLOC(info); git_vector_foreach(&files, i, file) { @@ -96,8 +107,6 @@ int git_attr_get_many( } pos = git_vector_bsearch(&rule->assigns, &info[k].name); - git_clearerror(); /* okay if search failed */ - if (pos >= 0) { info[k].found = (git_attr_assignment *) git_vector_get(&rule->assigns, pos); @@ -112,6 +121,7 @@ int git_attr_get_many( cleanup: git_vector_free(&files); + git_attr_path__free(&path); git__free(info); return error; @@ -119,7 +129,9 @@ cleanup: int git_attr_foreach( - git_repository *repo, const char *pathname, + git_repository *repo, + uint32_t flags, + const char *pathname, int (*callback)(const char *name, const char *value, void *payload), void *payload) { @@ -130,18 +142,16 @@ int git_attr_foreach( git_attr_file *file; git_attr_rule *rule; git_attr_assignment *assign; - git_hashtable *seen = NULL; + git_strmap *seen = NULL; - if ((error = git_attr_path__init( - &path, pathname, git_repository_workdir(repo))) < GIT_SUCCESS || - (error = collect_attr_files(repo, pathname, &files)) < GIT_SUCCESS) - return git__rethrow(error, "Could not get attributes for %s", pathname); + if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0) + return -1; - seen = git_hashtable_alloc(8, git_hash__strhash_cb, git_hash__strcmp_cb); - if (!seen) { - error = GIT_ENOMEM; + if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0) goto cleanup; - } + + seen = git_strmap_alloc(); + GITERR_CHECK_ALLOC(seen); git_vector_foreach(&files, i, file) { @@ -149,27 +159,23 @@ int git_attr_foreach( git_vector_foreach(&rule->assigns, k, assign) { /* skip if higher priority assignment was already seen */ - if (git_hashtable_lookup(seen, assign->name)) + if (git_strmap_exists(seen, assign->name)) continue; - error = git_hashtable_insert(seen, assign->name, assign); - if (error != GIT_SUCCESS) - goto cleanup; + git_strmap_insert(seen, assign->name, assign, error); + if (error >= 0) + error = callback(assign->name, assign->value, payload); - error = callback(assign->name, assign->value, payload); - if (error != GIT_SUCCESS) + if (error != 0) goto cleanup; } } } cleanup: - if (seen) - git_hashtable_free(seen); + git_strmap_free(seen); git_vector_free(&files); - - if (error != GIT_SUCCESS) - (void)git__rethrow(error, "Could not get attributes for %s", pathname); + git_attr_path__free(&path); return error; } @@ -182,124 +188,332 @@ int git_attr_add_macro( { int error; git_attr_rule *macro = NULL; + git_pool *pool; - if ((error = git_attr_cache__init(repo)) < GIT_SUCCESS) - return error; + if (git_attr_cache__init(repo) < 0) + return -1; macro = git__calloc(1, sizeof(git_attr_rule)); - if (!macro) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(macro); - macro->match.pattern = git__strdup(name); - if (!macro->match.pattern) { - git__free(macro); - return GIT_ENOMEM; - } + pool = &git_repository_attr_cache(repo)->pool; + + macro->match.pattern = git_pool_strdup(pool, name); + GITERR_CHECK_ALLOC(macro->match.pattern); macro->match.length = strlen(macro->match.pattern); macro->match.flags = GIT_ATTR_FNMATCH_MACRO; - error = git_attr_assignment__parse(repo, ¯o->assigns, &values); + error = git_attr_assignment__parse(repo, pool, ¯o->assigns, &values); - if (error == GIT_SUCCESS) + if (!error) error = git_attr_cache__insert_macro(repo, macro); - if (error < GIT_SUCCESS) + if (error < 0) git_attr_rule__free(macro); return error; } -int git_attr_cache__is_cached(git_repository *repo, const char *path) +bool git_attr_cache__is_cached( + git_repository *repo, git_attr_file_source source, const char *path) { - const char *cache_key = path; - if (repo && git__prefixcmp(cache_key, git_repository_workdir(repo)) == 0) - cache_key += strlen(git_repository_workdir(repo)); - return (git_hashtable_lookup(repo->attrcache.files, cache_key) == NULL); + 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_attr_file_stat_sig *sig, + const char *filename) +{ + int error; + git_buf content = GIT_BUF_INIT; + struct stat st; + + if (p_stat(filename, &st) < 0) + return GIT_ENOTFOUND; + + if (sig != NULL && + (git_time_t)st.st_mtime == sig->seconds && + (git_off_t)st.st_size == sig->size && + (unsigned int)st.st_ino == sig->ino) + return GIT_ENOTFOUND; + + error = git_futils_readbuffer_updated(&content, filename, NULL, NULL); + if (error < 0) + return error; + + if (sig != NULL) { + sig->seconds = (git_time_t)st.st_mtime; + sig->size = (git_off_t)st.st_size; + sig->ino = (unsigned int)st.st_ino; + } + + *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; + git_index *index; + git_index_entry *entry; + + if ((error = git_repository_index__weakptr(&index, repo)) < 0 || + (error = git_index_find(index, relfile)) < 0) + return error; + + entry = git_index_get(index, error); + + if (old_oid && git_oid_cmp(old_oid, &entry->oid) == 0) + return GIT_ENOTFOUND; + + if ((error = git_blob_lookup(blob, repo, &entry->oid)) < 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; + + cache_pos = git_strmap_lookup_index(cache->files, cache_key.ptr); + + git_buf_free(&cache_key); + + if (git_strmap_valid_index(cache->files, cache_pos)) + *file = git_strmap_value_at(cache->files, cache_pos); + + 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 = 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); + return 0; + } + + if (git_attr_file__new(file, 0, filename, &cache->pool) < 0) + return -1; + + git_strmap_insert(cache->files, (*file)->key + 2, *file, error); + if (error > 0) + error = 0; + + return error; } -/* add git_attr_file to vector of files, loading if needed */ int git_attr_cache__push_file( git_repository *repo, - git_vector *stack, - const char *base, - const char *filename, - int (*loader)(git_repository *, const char *, git_attr_file *)) + const char *base, + const char *filename, + git_attr_file_source source, + git_attr_file_parser parse, + git_vector *stack) { - int error = GIT_SUCCESS; - git_attr_cache *cache = &repo->attrcache; + 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; - int add_to_cache = 0; - const char *cache_key; + git_blob *blob = NULL; + git_attr_file_stat_sig st; - if (base != NULL) { - if ((error = git_buf_joinpath(&path, base, filename)) < GIT_SUCCESS) - goto cleanup; + 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; } - /* either get attr_file from cache or read from disk */ - cache_key = filename; - if (repo && git__prefixcmp(cache_key, git_repository_workdir(repo)) == 0) - cache_key += strlen(git_repository_workdir(repo)); - - file = git_hashtable_lookup(cache->files, cache_key); - if (file == NULL && git_path_exists(filename) == GIT_SUCCESS) { - if ((error = git_attr_file__new(&file)) == GIT_SUCCESS) { - if ((error = loader(repo, filename, file)) < GIT_SUCCESS) { - git_attr_file__free(file); - file = NULL; - } + 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) { + if (file) + memcpy(&st, &file->cache_data.st, sizeof(st)); + else + memset(&st, 0, sizeof(st)); + + error = load_attr_file(&content, &st, 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; } - add_to_cache = (error == GIT_SUCCESS); + goto finish; } - if (error == GIT_SUCCESS && file != NULL) { - /* add file to vector, if we found it */ + if (!file && + (error = git_attr_file__new(&file, source, relfile, &cache->pool)) < 0) + goto finish; + + if (parse && (error = parse(repo, content, file)) < 0) + goto finish; + + git_strmap_insert(cache->files, file->key, file, error); + if (error > 0) + error = 0; + + /* remember "cache buster" file signature */ + if (blob) + git_oid_cpy(&file->cache_data.oid, git_object_id((git_object *)blob)); + else + memcpy(&file->cache_data.st, &st, sizeof(st)); + +finish: + /* push file onto vector if we found one*/ + if (!error && file != NULL) error = git_vector_insert(stack, file); - /* add file to cache, if it is new */ - /* do this after above step b/c it is not critical */ - if (error == GIT_SUCCESS && add_to_cache && file->path != NULL) - error = git_hashtable_insert(cache->files, file->path, file); - } + if (error != 0) + git_attr_file__free(file); + + if (blob) + git_blob_free(blob); + else + git__free((void *)content); -cleanup: git_buf_free(&path); + return error; } -#define push_attrs(R,S,B,F) \ - git_attr_cache__push_file((R),(S),(B),(F),git_attr_file__from_file) +#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,(S)) typedef struct { git_repository *repo; + uint32_t flags; + const char *workdir; + git_index *index; 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) +{ + int count = 0; + + switch (flags & 0x03) { + case GIT_ATTR_CHECK_FILE_THEN_INDEX: + if (has_wd) + srcs[count++] = GIT_ATTR_FILE_FROM_FILE; + if (has_index) + srcs[count++] = GIT_ATTR_FILE_FROM_INDEX; + break; + case GIT_ATTR_CHECK_INDEX_THEN_FILE: + if (has_index) + srcs[count++] = GIT_ATTR_FILE_FROM_INDEX; + if (has_wd) + srcs[count++] = GIT_ATTR_FILE_FROM_FILE; + break; + case GIT_ATTR_CHECK_INDEX_ONLY: + if (has_index) + srcs[count++] = GIT_ATTR_FILE_FROM_INDEX; + break; + } + + return count; +} + 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; - return push_attrs(info->repo, info->files, path->ptr, GIT_ATTR_FILE); + git_attr_file_source src[2]; + + n_src = git_attr_cache__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, info->files); + + return error; } static int collect_attr_files( - git_repository *repo, const char *path, git_vector *files) + git_repository *repo, + uint32_t flags, + const char *path, + git_vector *files) { - int error = GIT_SUCCESS; + int error; git_buf dir = GIT_BUF_INIT; - git_config *cfg; const char *workdir = git_repository_workdir(repo); attr_walk_up_info info; - if ((error = git_attr_cache__init(repo)) < GIT_SUCCESS) - goto cleanup; - - if ((error = git_vector_init(files, 4, NULL)) < GIT_SUCCESS) - goto cleanup; + if (git_attr_cache__init(repo) < 0 || + git_vector_init(files, 4, NULL) < 0) + return -1; - if ((error = git_path_find_dir(&dir, path, workdir)) < GIT_SUCCESS) + /* Resolve path in a non-bare repo */ + if (workdir != NULL) + error = git_path_find_dir(&dir, path, workdir); + else + error = git_path_dirname_r(&dir, path); + if (error < 0) goto cleanup; /* in precendence order highest to lowest: @@ -309,38 +523,40 @@ static int collect_attr_files( * - $GIT_PREFIX/etc/gitattributes */ - error = push_attrs(repo, files, repo->path_repository, GIT_ATTR_FILE_INREPO); - if (error < GIT_SUCCESS) + error = push_attr_file( + repo, files, git_repository_path(repo), GIT_ATTR_FILE_INREPO); + if (error < 0) goto cleanup; - info.repo = repo; + info.repo = repo; + info.flags = flags; + info.workdir = workdir; + if (git_repository_index__weakptr(&info.index, repo) < 0) + giterr_clear(); /* no error even if there is no index */ info.files = files; + error = git_path_walk_up(&dir, workdir, push_one_attr, &info); - if (error < GIT_SUCCESS) + if (error < 0) goto cleanup; - if ((error = git_repository_config(&cfg, repo)) == GIT_SUCCESS) { - const char *core_attribs = NULL; - git_config_get_string(cfg, GIT_ATTR_CONFIG, &core_attribs); - git_clearerror(); /* don't care if attributesfile is not set */ - if (core_attribs) - error = push_attrs(repo, files, NULL, core_attribs); - git_config_free(cfg); + if (git_repository_attr_cache(repo)->cfg_attr_file != NULL) { + error = push_attr_file( + repo, files, NULL, git_repository_attr_cache(repo)->cfg_attr_file); + if (error < 0) + goto cleanup; } - if (error == GIT_SUCCESS) { + if ((flags & GIT_ATTR_CHECK_NO_SYSTEM) == 0) { error = git_futils_find_system_file(&dir, GIT_ATTR_FILE_SYSTEM); - if (error == GIT_SUCCESS) - error = push_attrs(repo, files, NULL, dir.ptr); + if (!error) + error = push_attr_file(repo, files, NULL, dir.ptr); else if (error == GIT_ENOTFOUND) - error = GIT_SUCCESS; + error = 0; } cleanup: - if (error < GIT_SUCCESS) { - git__rethrow(error, "Could not get attributes for '%s'", path); + if (error < 0) git_vector_free(files); - } git_buf_free(&dir); return error; @@ -349,70 +565,108 @@ static int collect_attr_files( int git_attr_cache__init(git_repository *repo) { - int error = GIT_SUCCESS; - git_attr_cache *cache = &repo->attrcache; + int ret; + git_attr_cache *cache = git_repository_attr_cache(repo); + git_config *cfg; if (cache->initialized) - return GIT_SUCCESS; + return 0; + + /* cache config settings for attributes and ignores */ + if (git_repository_config__weakptr(&cfg, repo) < 0) + return -1; + + ret = git_config_get_string(&cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG); + if (ret < 0 && ret != GIT_ENOTFOUND) + return ret; + + ret = git_config_get_string(&cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG); + if (ret < 0 && ret != GIT_ENOTFOUND) + return ret; + + giterr_clear(); + /* allocate hashtable for attribute and ignore file contents */ if (cache->files == NULL) { - cache->files = git_hashtable_alloc( - 8, git_hash__strhash_cb, git_hash__strcmp_cb); - if (!cache->files) - return git__throw(GIT_ENOMEM, "Could not initialize attribute cache"); + cache->files = git_strmap_alloc(); + GITERR_CHECK_ALLOC(cache->files); } + /* allocate hashtable for attribute macros */ if (cache->macros == NULL) { - cache->macros = git_hashtable_alloc( - 8, git_hash__strhash_cb, git_hash__strcmp_cb); - if (!cache->macros) - return git__throw(GIT_ENOMEM, "Could not initialize attribute cache"); + cache->macros = git_strmap_alloc(); + GITERR_CHECK_ALLOC(cache->macros); } + /* allocate string pool */ + if (git_pool_init(&cache->pool, 1, 0) < 0) + return -1; + cache->initialized = 1; /* insert default macros */ - error = git_attr_add_macro(repo, "binary", "-diff -crlf"); - - return error; + return git_attr_add_macro(repo, "binary", "-diff -crlf -text"); } void git_attr_cache_flush( git_repository *repo) { + git_attr_cache *cache; + if (!repo) return; - if (repo->attrcache.files) { - const void *GIT_UNUSED(name); + cache = git_repository_attr_cache(repo); + + if (cache->files != NULL) { git_attr_file *file; - GIT_HASHTABLE_FOREACH(repo->attrcache.files, name, file, - git_attr_file__free(file)); + git_strmap_foreach_value(cache->files, file, { + git_attr_file__free(file); + }); - git_hashtable_free(repo->attrcache.files); - repo->attrcache.files = NULL; + git_strmap_free(cache->files); } - if (repo->attrcache.macros) { - const void *GIT_UNUSED(name); + if (cache->macros != NULL) { git_attr_rule *rule; - GIT_HASHTABLE_FOREACH(repo->attrcache.macros, name, rule, - git_attr_rule__free(rule)); + git_strmap_foreach_value(cache->macros, rule, { + git_attr_rule__free(rule); + }); - git_hashtable_free(repo->attrcache.macros); - repo->attrcache.macros = NULL; + git_strmap_free(cache->macros); } - repo->attrcache.initialized = 0; + git_pool_clear(&cache->pool); + + cache->initialized = 0; } int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro) { + git_strmap *macros = git_repository_attr_cache(repo)->macros; + int error; + + /* TODO: generate warning log if (macro->assigns.length == 0) */ if (macro->assigns.length == 0) - return git__throw(GIT_EMISSINGOBJDATA, "git attribute macro with no values"); + return 0; - return git_hashtable_insert( - repo->attrcache.macros, macro->match.pattern, macro); + git_strmap_insert(macros, macro->match.pattern, macro, error); + 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); +} + |