diff options
author | Vicent Martà <tanoku@gmail.com> | 2012-05-02 15:59:02 -0700 |
---|---|---|
committer | Vicent Martà <tanoku@gmail.com> | 2012-05-02 15:59:02 -0700 |
commit | 40879facad0337d954d4904e212af3b36cdb9465 (patch) | |
tree | aea730551948c67bb1fb88098cf8a67d3ed3211d /src | |
parent | 2218fd57a50ceb851cb131939bf0747e072e40f6 (diff) | |
parent | 3fd99be98a91416dae77d65fe593965a0723fa8c (diff) | |
download | libgit2-40879facad0337d954d4904e212af3b36cdb9465.tar.gz |
Merge branch 'new-error-handling' into development
Conflicts:
.travis.yml
include/git2/diff.h
src/config_file.c
src/diff.c
src/diff_output.c
src/mwindow.c
src/path.c
tests-clar/clar_helpers.c
tests-clar/object/tree/frompath.c
tests/t00-core.c
tests/t03-objwrite.c
tests/t08-tag.c
tests/t10-refs.c
tests/t12-repo.c
tests/t18-status.c
tests/test_helpers.c
tests/test_main.c
Diffstat (limited to 'src')
-rw-r--r-- | src/attr.c | 277 | ||||
-rw-r--r-- | src/attr.h | 18 | ||||
-rw-r--r-- | src/attr_file.c | 228 | ||||
-rw-r--r-- | src/attr_file.h | 29 | ||||
-rw-r--r-- | src/blob.c | 116 | ||||
-rw-r--r-- | src/branch.c | 208 | ||||
-rw-r--r-- | src/branch.h | 17 | ||||
-rw-r--r-- | src/buffer.c | 77 | ||||
-rw-r--r-- | src/buffer.h | 38 | ||||
-rw-r--r-- | src/cache.c | 16 | ||||
-rw-r--r-- | src/cc-compat.h | 8 | ||||
-rw-r--r-- | src/commit.c | 186 | ||||
-rw-r--r-- | src/common.h | 12 | ||||
-rw-r--r-- | src/config.c | 274 | ||||
-rw-r--r-- | src/config.h | 5 | ||||
-rw-r--r-- | src/config_cache.c | 1 | ||||
-rw-r--r-- | src/config_file.c | 874 | ||||
-rw-r--r-- | src/config_file.h | 31 | ||||
-rw-r--r-- | src/crlf.c | 7 | ||||
-rw-r--r-- | src/delta-apply.c | 15 | ||||
-rw-r--r-- | src/diff.c | 518 | ||||
-rw-r--r-- | src/diff.h | 15 | ||||
-rw-r--r-- | src/diff_output.c | 242 | ||||
-rw-r--r-- | src/errors.c | 110 | ||||
-rw-r--r-- | src/fetch.c | 152 | ||||
-rw-r--r-- | src/fetch.h | 7 | ||||
-rw-r--r-- | src/filebuf.c | 241 | ||||
-rw-r--r-- | src/filebuf.h | 25 | ||||
-rw-r--r-- | src/fileops.c | 262 | ||||
-rw-r--r-- | src/fileops.h | 41 | ||||
-rw-r--r-- | src/filter.c | 16 | ||||
-rw-r--r-- | src/global.c | 6 | ||||
-rw-r--r-- | src/global.h | 3 | ||||
-rw-r--r-- | src/hashtable.c | 258 | ||||
-rw-r--r-- | src/hashtable.h | 95 | ||||
-rw-r--r-- | src/ignore.c | 122 | ||||
-rw-r--r-- | src/index.c | 326 | ||||
-rw-r--r-- | src/index.h | 2 | ||||
-rw-r--r-- | src/indexer.c | 653 | ||||
-rw-r--r-- | src/iterator.c | 145 | ||||
-rw-r--r-- | src/khash.h | 608 | ||||
-rw-r--r-- | src/map.h | 5 | ||||
-rw-r--r-- | src/mwindow.c | 54 | ||||
-rw-r--r-- | src/mwindow.h | 6 | ||||
-rw-r--r-- | src/netops.c | 106 | ||||
-rw-r--r-- | src/netops.h | 10 | ||||
-rw-r--r-- | src/notes.c | 179 | ||||
-rw-r--r-- | src/object.c | 30 | ||||
-rw-r--r-- | src/odb.c | 312 | ||||
-rw-r--r-- | src/odb.h | 10 | ||||
-rw-r--r-- | src/odb_loose.c | 352 | ||||
-rw-r--r-- | src/odb_pack.c | 172 | ||||
-rw-r--r-- | src/oid.c | 77 | ||||
-rw-r--r-- | src/oidmap.h | 42 | ||||
-rw-r--r-- | src/pack.c | 340 | ||||
-rw-r--r-- | src/pack.h | 23 | ||||
-rw-r--r-- | src/path.c | 267 | ||||
-rw-r--r-- | src/path.h | 37 | ||||
-rw-r--r-- | src/pkt.c | 217 | ||||
-rw-r--r-- | src/pkt.h | 12 | ||||
-rw-r--r-- | src/pool.c | 294 | ||||
-rw-r--r-- | src/pool.h | 125 | ||||
-rw-r--r-- | src/posix.c | 38 | ||||
-rw-r--r-- | src/posix.h | 8 | ||||
-rw-r--r-- | src/pqueue.c | 12 | ||||
-rw-r--r-- | src/protocol.c | 30 | ||||
-rw-r--r-- | src/reflog.c | 177 | ||||
-rw-r--r-- | src/refs.c | 1191 | ||||
-rw-r--r-- | src/refs.h | 27 | ||||
-rw-r--r-- | src/refspec.c | 45 | ||||
-rw-r--r-- | src/remote.c | 298 | ||||
-rw-r--r-- | src/repository.c | 935 | ||||
-rw-r--r-- | src/repository.h | 13 | ||||
-rw-r--r-- | src/revwalk.c | 554 | ||||
-rw-r--r-- | src/sha1.c | 4 | ||||
-rw-r--r-- | src/sha1.h | 2 | ||||
-rw-r--r-- | src/sha1_lookup.c | 3 | ||||
-rw-r--r-- | src/signature.c | 125 | ||||
-rw-r--r-- | src/status.c | 833 | ||||
-rw-r--r-- | src/strmap.h | 54 | ||||
-rw-r--r-- | src/submodule.c | 384 | ||||
-rw-r--r-- | src/tag.c | 244 | ||||
-rw-r--r-- | src/transport.c | 8 | ||||
-rw-r--r-- | src/transport.h | 11 | ||||
-rw-r--r-- | src/transports/git.c | 325 | ||||
-rw-r--r-- | src/transports/http.c | 369 | ||||
-rw-r--r-- | src/transports/local.c | 141 | ||||
-rw-r--r-- | src/tree.c | 287 | ||||
-rw-r--r-- | src/tree.h | 2 | ||||
-rw-r--r-- | src/tsort.c | 28 | ||||
-rw-r--r-- | src/unix/map.c | 30 | ||||
-rw-r--r-- | src/util.c | 78 | ||||
-rw-r--r-- | src/util.h | 36 | ||||
-rw-r--r-- | src/vector.c | 65 | ||||
-rw-r--r-- | src/vector.h | 2 | ||||
-rw-r--r-- | src/win32/dir.c | 88 | ||||
-rw-r--r-- | src/win32/map.c | 52 | ||||
-rw-r--r-- | src/win32/posix.h | 2 | ||||
-rw-r--r-- | src/win32/posix_w32.c | 232 | ||||
-rw-r--r-- | src/win32/pthread.c | 13 | ||||
-rw-r--r-- | src/win32/utf-conv.c | 41 | ||||
-rw-r--r-- | src/xdiff/xemit.c | 2 | ||||
-rw-r--r-- | src/xdiff/xmerge.c | 6 | ||||
-rw-r--r-- | src/xdiff/xutils.c | 6 |
104 files changed, 8877 insertions, 6878 deletions
diff --git a/src/attr.c b/src/attr.c index 603498df2..120d12737 100644 --- a/src/attr.c +++ b/src/attr.c @@ -3,6 +3,8 @@ #include "config.h" #include <ctype.h> +GIT__USE_STRMAP; + static int collect_attr_files( git_repository *repo, const char *path, git_vector *files); @@ -21,10 +23,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, pathname, &files)) < 0) + goto cleanup; attr.name = name; attr.name_hash = git_attr_file__name_hash(name); @@ -33,18 +36,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; } @@ -70,15 +72,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, 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 +97,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 +111,7 @@ int git_attr_get_many( cleanup: git_vector_free(&files); + git_attr_path__free(&path); git__free(info); return error; @@ -130,18 +130,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, pathname, &files)) < 0) goto cleanup; - } + + seen = git_strmap_alloc(); + GITERR_CHECK_ALLOC(seen); git_vector_foreach(&files, i, file) { @@ -149,27 +147,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,40 +176,42 @@ 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, const char *path) { const char *cache_key = path; + git_strmap *files = git_repository_attr_cache(repo)->files; + 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); + + return git_strmap_exists(files, cache_key); } int git_attr_cache__lookup_or_create_file( @@ -226,32 +222,36 @@ int git_attr_cache__lookup_or_create_file( git_attr_file **file_ptr) { int error; - git_attr_cache *cache = &repo->attrcache; + git_attr_cache *cache = git_repository_attr_cache(repo); git_attr_file *file = NULL; + khiter_t pos; - file = git_hashtable_lookup(cache->files, key); - if (file) { - *file_ptr = file; - return GIT_SUCCESS; + pos = git_strmap_lookup_index(cache->files, key); + if (git_strmap_valid_index(cache->files, pos)) { + *file_ptr = git_strmap_value_at(cache->files, pos); + return 0; } - if (loader && git_path_exists(filename) != GIT_SUCCESS) { + if (loader && git_path_exists(filename) == false) { *file_ptr = NULL; - return GIT_SUCCESS; + return 0; } - if ((error = git_attr_file__new(&file)) < GIT_SUCCESS) - return error; + if (git_attr_file__new(&file, &cache->pool) < 0) + return -1; if (loader) error = loader(repo, filename, file); else error = git_attr_file__set_path(repo, key, file); - if (error == GIT_SUCCESS) - error = git_hashtable_insert(cache->files, file->path, file); + if (!error) { + git_strmap_insert(cache->files, file->path, file, error); + if (error > 0) + error = 0; + } - if (error < GIT_SUCCESS) { + if (error < 0) { git_attr_file__free(file); file = NULL; } @@ -274,8 +274,8 @@ int git_attr_cache__push_file( const char *cache_key; if (base != NULL) { - if ((error = git_buf_joinpath(&path, base, filename)) < GIT_SUCCESS) - return error; + if (git_buf_joinpath(&path, base, filename) < 0) + return -1; filename = path.ptr; } @@ -287,7 +287,7 @@ int git_attr_cache__push_file( error = git_attr_cache__lookup_or_create_file( repo, cache_key, filename, loader, &file); - if (error == GIT_SUCCESS && file != NULL) + if (!error && file != NULL) error = git_vector_insert(stack, file); git_buf_free(&path); @@ -311,19 +311,17 @@ static int push_one_attr(void *ref, git_buf *path) static int collect_attr_files( git_repository *repo, 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) + error = git_path_find_dir(&dir, path, workdir); + if (error < 0) goto cleanup; /* in precendence order highest to lowest: @@ -333,38 +331,33 @@ 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_attrs( + repo, files, git_repository_path(repo), GIT_ATTR_FILE_INREPO); + if (error < 0) goto cleanup; info.repo = repo; 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_attrs( + repo, files, NULL, git_repository_attr_cache(repo)->cfg_attr_file); + if (error < 0) + goto cleanup; } - if (error == GIT_SUCCESS) { - error = git_futils_find_system_file(&dir, GIT_ATTR_FILE_SYSTEM); - if (error == GIT_SUCCESS) - error = push_attrs(repo, files, NULL, dir.ptr); - else if (error == GIT_ENOTFOUND) - error = GIT_SUCCESS; - } + error = git_futils_find_system_file(&dir, GIT_ATTR_FILE_SYSTEM); + if (!error) + error = push_attrs(repo, files, NULL, dir.ptr); + else if (error == GIT_ENOTFOUND) + 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; @@ -373,68 +366,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(cfg, GIT_ATTR_CONFIG, &cache->cfg_attr_file); + if (ret < 0 && ret != GIT_ENOTFOUND) + return ret; + + ret = git_config_get_string(cfg, GIT_IGNORE_CONFIG, &cache->cfg_excl_file); + 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) { + cache = git_repository_attr_cache(repo); + + if (cache->files != NULL) { git_attr_file *file; - GIT_HASHTABLE_FOREACH_VALUE(repo->attrcache.files, 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) { + if (cache->macros != NULL) { git_attr_rule *rule; - GIT_HASHTABLE_FOREACH_VALUE(repo->attrcache.macros, 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); +} + diff --git a/src/attr.h b/src/attr.h index 5dbbb2366..43caf1b81 100644 --- a/src/attr.h +++ b/src/attr.h @@ -8,11 +8,18 @@ #define INCLUDE_attr_h__ #include "attr_file.h" +#include "strmap.h" + +#define GIT_ATTR_CONFIG "core.attributesfile" +#define GIT_IGNORE_CONFIG "core.excludesfile" typedef struct { int initialized; - git_hashtable *files; /* hash path to git_attr_file of rules */ - git_hashtable *macros; /* hash name to vector<git_attr_assignment> */ + git_pool pool; + git_strmap *files; /* hash path to git_attr_file of rules */ + git_strmap *macros; /* hash name to vector<git_attr_assignment> */ + const char *cfg_attr_file; /* cached value of core.attributesfile */ + const char *cfg_excl_file; /* cached value of core.excludesfile */ } git_attr_cache; extern int git_attr_cache__init(git_repository *repo); @@ -20,6 +27,9 @@ extern int git_attr_cache__init(git_repository *repo); 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__lookup_or_create_file( git_repository *repo, const char *key, @@ -34,7 +44,7 @@ extern int git_attr_cache__push_file( const char *filename, int (*loader)(git_repository *, const char *, git_attr_file *)); -/* returns GIT_SUCCESS if path is in cache */ -extern int git_attr_cache__is_cached(git_repository *repo, const char *path); +/* returns true if path is in cache */ +extern bool git_attr_cache__is_cached(git_repository *repo, const char *path); #endif diff --git a/src/attr_file.c b/src/attr_file.c index 3783b5ef3..25c21b1fd 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -9,26 +9,32 @@ const char *git_attr__false = "[internal]__FALSE__"; static int sort_by_hash_and_name(const void *a_raw, const void *b_raw); static void git_attr_rule__clear(git_attr_rule *rule); -int git_attr_file__new(git_attr_file **attrs_ptr) +int git_attr_file__new(git_attr_file **attrs_ptr, git_pool *pool) { - int error; git_attr_file *attrs = NULL; attrs = git__calloc(1, sizeof(git_attr_file)); - if (attrs == NULL) - error = GIT_ENOMEM; - else - error = git_vector_init(&attrs->rules, 4, NULL); + GITERR_CHECK_ALLOC(attrs); - if (error != GIT_SUCCESS) { - git__rethrow(error, "Could not allocate attribute storage"); - git__free(attrs); - attrs = NULL; + 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_vector_init(&attrs->rules, 4, NULL) < 0) + goto fail; + *attrs_ptr = attrs; + return 0; - return error; +fail: + git_attr_file__free(attrs); + attrs_ptr = NULL; + return -1; } int git_attr_file__set_path( @@ -50,13 +56,15 @@ int git_attr_file__set_path( file->path = git__strdup(path); } - return (file->path == NULL) ? GIT_ENOMEM : GIT_SUCCESS; + GITERR_CHECK_ALLOC(file->path); + + return 0; } int git_attr_file__from_buffer( git_repository *repo, const char *buffer, git_attr_file *attrs) { - int error = GIT_SUCCESS; + int error = 0; const char *scan = NULL; char *context = NULL; git_attr_rule *rule = NULL; @@ -68,19 +76,21 @@ int git_attr_file__from_buffer( if (attrs->path && git__suffixcmp(attrs->path, GIT_ATTR_FILE) == 0) { context = git__strndup(attrs->path, strlen(attrs->path) - strlen(GIT_ATTR_FILE)); - if (!context) error = GIT_ENOMEM; + GITERR_CHECK_ALLOC(context); } - while (error == GIT_SUCCESS && *scan) { + while (!error && *scan) { /* allocate rule if needed */ if (!rule && !(rule = git__calloc(1, sizeof(git_attr_rule)))) { - error = GIT_ENOMEM; + error = -1; break; } /* parse the next "pattern attr attr attr" line */ - if (!(error = git_attr_fnmatch__parse(&rule->match, context, &scan)) && - !(error = git_attr_assignment__parse(repo, &rule->assigns, &scan))) + if (!(error = git_attr_fnmatch__parse( + &rule->match, attrs->pool, context, &scan)) && + !(error = git_attr_assignment__parse( + repo, attrs->pool, &rule->assigns, &scan))) { if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO) /* should generate error/warning if this is coming from any @@ -92,10 +102,10 @@ int git_attr_file__from_buffer( } /* if the rule wasn't a pattern, on to the next */ - if (error != GIT_SUCCESS) { + if (error < 0) { git_attr_rule__clear(rule); /* reset rule contents */ if (error == GIT_ENOTFOUND) - error = GIT_SUCCESS; + error = 0; } else { rule = NULL; /* vector now "owns" the rule */ } @@ -110,21 +120,20 @@ int git_attr_file__from_buffer( int git_attr_file__from_file( git_repository *repo, const char *path, git_attr_file *file) { - int error = GIT_SUCCESS; + int error; git_buf fbuf = GIT_BUF_INIT; assert(path && file); - if (file->path == NULL) - error = git_attr_file__set_path(repo, path, file); + if (file->path == NULL && git_attr_file__set_path(repo, path, file) < 0) + return -1; + + if (git_futils_readbuffer(&fbuf, path) < 0) + return -1; - if (error == GIT_SUCCESS && - (error = git_futils_readbuffer(&fbuf, path)) == GIT_SUCCESS) - error = git_attr_file__from_buffer(repo, fbuf.ptr, file); + error = git_attr_file__from_buffer(repo, fbuf.ptr, file); git_buf_free(&fbuf); - if (error != GIT_SUCCESS) - git__rethrow(error, "Could not open attribute file '%s'", path); return error; } @@ -145,12 +154,18 @@ void git_attr_file__free(git_attr_file *file) git__free(file->path); file->path = NULL; + if (file->pool_is_allocated) { + git_pool_clear(file->pool); + git__free(file->pool); + } + file->pool = NULL; + git__free(file); } -unsigned long git_attr_file__name_hash(const char *name) +uint32_t git_attr_file__name_hash(const char *name) { - unsigned long h = 5381; + uint32_t h = 5381; int c; assert(name); while ((c = (int)*name++) != 0) @@ -176,7 +191,6 @@ int git_attr_file__lookup_one( git_attr_file__foreach_matching_rule(file, path, i, rule) { int pos = git_vector_bsearch(&rule->assigns, &name); - git_clearerror(); /* okay if search failed */ if (pos >= 0) { *value = ((git_attr_assignment *) @@ -185,37 +199,37 @@ int git_attr_file__lookup_one( } } - return GIT_SUCCESS; + return 0; } -int git_attr_fnmatch__match( +bool git_attr_fnmatch__match( git_attr_fnmatch *match, const git_attr_path *path) { - int matched = FNM_NOMATCH; + int fnm; if (match->flags & GIT_ATTR_FNMATCH_DIRECTORY && !path->is_dir) - return matched; + return false; if (match->flags & GIT_ATTR_FNMATCH_FULLPATH) - matched = p_fnmatch(match->pattern, path->path, FNM_PATHNAME); + fnm = p_fnmatch(match->pattern, path->path, FNM_PATHNAME); else if (path->is_dir) - matched = p_fnmatch(match->pattern, path->basename, FNM_LEADING_DIR); + fnm = p_fnmatch(match->pattern, path->basename, FNM_LEADING_DIR); else - matched = p_fnmatch(match->pattern, path->basename, 0); + fnm = p_fnmatch(match->pattern, path->basename, 0); - return matched; + return (fnm == FNM_NOMATCH) ? false : true; } -int git_attr_rule__match( +bool git_attr_rule__match( git_attr_rule *rule, const git_attr_path *path) { - int matched = git_attr_fnmatch__match(&rule->match, path); + bool matched = git_attr_fnmatch__match(&rule->match, path); if (rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE) - matched = (matched == GIT_SUCCESS) ? FNM_NOMATCH : GIT_SUCCESS; + matched = !matched; return matched; } @@ -230,7 +244,6 @@ git_attr_assignment *git_attr_rule__lookup_assignment( key.name_hash = git_attr_file__name_hash(name); pos = git_vector_bsearch(&rule->assigns, &key); - git_clearerror(); /* okay if search failed */ return (pos >= 0) ? git_vector_get(&rule->assigns, pos) : NULL; } @@ -238,25 +251,48 @@ git_attr_assignment *git_attr_rule__lookup_assignment( int git_attr_path__init( git_attr_path *info, const char *path, const char *base) { - assert(info && path); - info->path = path; - info->basename = strrchr(path, '/'); + /* build full path as best we can */ + git_buf_init(&info->full, 0); + + if (base != NULL && git_path_root(path) < 0) { + if (git_buf_joinpath(&info->full, base, path) < 0) + return -1; + info->path = info->full.ptr + strlen(base); + } else { + if (git_buf_sets(&info->full, path) < 0) + return -1; + info->path = info->full.ptr; + } + + /* remove trailing slashes */ + while (info->full.size > 0) { + if (info->full.ptr[info->full.size - 1] != '/') + break; + info->full.size--; + } + info->full.ptr[info->full.size] = '\0'; + + /* skip leading slashes in path */ + while (*info->path == '/') + info->path++; + + /* find trailing basename component */ + info->basename = strrchr(info->path, '/'); if (info->basename) info->basename++; if (!info->basename || !*info->basename) - info->basename = path; + info->basename = info->path; - if (base != NULL && git_path_root(path) < 0) { - git_buf full_path = GIT_BUF_INIT; - int error = git_buf_joinpath(&full_path, base, path); - if (error == GIT_SUCCESS) - info->is_dir = (git_path_isdir(full_path.ptr) == GIT_SUCCESS); - git_buf_free(&full_path); - return error; - } - info->is_dir = (git_path_isdir(path) == GIT_SUCCESS); + info->is_dir = (int)git_path_isdir(info->full.ptr); + + return 0; +} - return GIT_SUCCESS; +void git_attr_path__free(git_attr_path *info) +{ + git_buf_free(&info->full); + info->path = NULL; + info->basename = NULL; } @@ -293,12 +329,13 @@ int git_attr_path__init( */ /* - * This will return GIT_SUCCESS if the spec was filled out, + * This will return 0 if the spec was filled out, * GIT_ENOTFOUND if the fnmatch does not require matching, or * another error code there was an actual problem. */ int git_attr_fnmatch__parse( git_attr_fnmatch *spec, + git_pool *pool, const char *source, const char **base) { @@ -339,7 +376,13 @@ int git_attr_fnmatch__parse( if (*scan == '/') { spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH; slash_count++; + if (pattern == scan) + pattern++; } + /* remember if we see an unescaped wildcard in pattern */ + else if ((*scan == '*' || *scan == '.' || *scan == '[') && + (scan == pattern || (*(scan - 1) != '\\'))) + spec->flags = spec->flags | GIT_ATTR_FNMATCH_HASWILD; } *base = scan; @@ -360,7 +403,8 @@ int git_attr_fnmatch__parse( /* 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__malloc(sourcelen + spec->length + 1); + spec->pattern = git_pool_malloc( + pool, (uint32_t)(sourcelen + spec->length + 1)); if (spec->pattern) { memcpy(spec->pattern, source, sourcelen); memcpy(spec->pattern + sourcelen, pattern, spec->length); @@ -368,12 +412,12 @@ int git_attr_fnmatch__parse( spec->pattern[spec->length] = '\0'; } } else { - spec->pattern = git__strndup(pattern, spec->length); + spec->pattern = git_pool_strndup(pool, pattern, spec->length); } if (!spec->pattern) { *base = git__next_line(pattern); - return GIT_ENOMEM; + return -1; } else { /* strip '\' that might have be used for internal whitespace */ char *to = spec->pattern; @@ -389,7 +433,7 @@ int git_attr_fnmatch__parse( } } - return GIT_SUCCESS; + return 0; } static int sort_by_hash_and_name(const void *a_raw, const void *b_raw) @@ -407,14 +451,11 @@ static int sort_by_hash_and_name(const void *a_raw, const void *b_raw) static void git_attr_assignment__free(git_attr_assignment *assign) { - git__free(assign->name); + /* name and value are stored in a git_pool associated with the + * git_attr_file, so they do not need to be freed here + */ assign->name = NULL; - - if (assign->is_allocated) { - git__free((void *)assign->value); - assign->value = NULL; - } - + assign->value = NULL; git__free(assign); } @@ -430,10 +471,11 @@ static int merge_assignments(void **old_raw, void *new_raw) int git_attr_assignment__parse( git_repository *repo, + git_pool *pool, git_vector *assigns, const char **base) { - int error = GIT_SUCCESS; + int error; const char *scan = *base; git_attr_assignment *assign = NULL; @@ -441,7 +483,7 @@ int git_attr_assignment__parse( assigns->_cmp = sort_by_hash_and_name; - while (*scan && *scan != '\n' && error == GIT_SUCCESS) { + while (*scan && *scan != '\n') { const char *name_start, *value_start; /* skip leading blanks */ @@ -450,16 +492,12 @@ int git_attr_assignment__parse( /* allocate assign if needed */ if (!assign) { assign = git__calloc(1, sizeof(git_attr_assignment)); - if (!assign) { - error = GIT_ENOMEM; - break; - } + GITERR_CHECK_ALLOC(assign); GIT_REFCOUNT_INC(assign); } assign->name_hash = 5381; assign->value = git_attr__true; - assign->is_allocated = 0; /* look for magic name prefixes */ if (*scan == '-') { @@ -487,11 +525,8 @@ int git_attr_assignment__parse( } /* allocate permanent storage for name */ - assign->name = git__strndup(name_start, scan - name_start); - if (!assign->name) { - error = GIT_ENOMEM; - break; - } + assign->name = git_pool_strndup(pool, name_start, scan - name_start); + GITERR_CHECK_ALLOC(assign->name); /* if there is an equals sign, find the value */ if (*scan == '=') { @@ -499,20 +534,15 @@ int git_attr_assignment__parse( /* if we found a value, allocate permanent storage for it */ if (scan > value_start) { - assign->value = git__strndup(value_start, scan - value_start); - if (!assign->value) { - error = GIT_ENOMEM; - break; - } else { - assign->is_allocated = 1; - } + assign->value = git_pool_strndup(pool, value_start, scan - value_start); + GITERR_CHECK_ALLOC(assign->value); } } /* expand macros (if given a repo with a macro cache) */ if (repo != NULL && assign->value == git_attr__true) { git_attr_rule *macro = - git_hashtable_lookup(repo->attrcache.macros, assign->name); + git_attr_cache__lookup_macro(repo, assign->name); if (macro != NULL) { unsigned int i; @@ -523,35 +553,27 @@ int git_attr_assignment__parse( error = git_vector_insert_sorted( assigns, massign, &merge_assignments); - - if (error == GIT_EEXISTS) - error = GIT_SUCCESS; - else if (error != GIT_SUCCESS) - break; + if (error < 0 && error != GIT_EEXISTS) + return error; } } } /* insert allocated assign into vector */ error = git_vector_insert_sorted(assigns, assign, &merge_assignments); - if (error == GIT_EEXISTS) - error = GIT_SUCCESS; - else if (error < GIT_SUCCESS) - break; + if (error < 0 && error != GIT_EEXISTS) + return error; /* clear assign since it is now "owned" by the vector */ assign = NULL; } - if (!assigns->length) - error = git__throw(GIT_ENOTFOUND, "No attribute assignments found for rule"); - if (assign != NULL) git_attr_assignment__free(assign); *base = git__next_line(scan); - return error; + return (assigns->length == 0) ? GIT_ENOTFOUND : 0; } static void git_attr_rule__clear(git_attr_rule *rule) @@ -568,7 +590,7 @@ static void git_attr_rule__clear(git_attr_rule *rule) git_vector_free(&rule->assigns); } - git__free(rule->match.pattern); + /* match.pattern is stored in a git_pool, so no need to free */ rule->match.pattern = NULL; rule->match.length = 0; } diff --git a/src/attr_file.h b/src/attr_file.h index 1ba18f9e4..10851bc49 100644 --- a/src/attr_file.h +++ b/src/attr_file.h @@ -9,18 +9,19 @@ #include "git2/attr.h" #include "vector.h" -#include "hashtable.h" +#include "pool.h" +#include "buffer.h" #define GIT_ATTR_FILE ".gitattributes" #define GIT_ATTR_FILE_INREPO "info/attributes" #define GIT_ATTR_FILE_SYSTEM "gitattributes" -#define GIT_ATTR_CONFIG "core.attributesfile" #define GIT_ATTR_FNMATCH_NEGATIVE (1U << 0) #define GIT_ATTR_FNMATCH_DIRECTORY (1U << 1) #define GIT_ATTR_FNMATCH_FULLPATH (1U << 2) #define GIT_ATTR_FNMATCH_MACRO (1U << 3) #define GIT_ATTR_FNMATCH_IGNORE (1U << 4) +#define GIT_ATTR_FNMATCH_HASWILD (1U << 5) typedef struct { char *pattern; @@ -36,33 +37,35 @@ typedef struct { typedef struct { git_refcount unused; const char *name; - unsigned long name_hash; + uint32_t name_hash; } git_attr_name; typedef struct { git_refcount rc; /* for macros */ char *name; - unsigned long name_hash; + uint32_t name_hash; const char *value; - int is_allocated; } git_attr_assignment; typedef struct { char *path; /* cache the path this was loaded from */ git_vector rules; /* vector of <rule*> or <fnmatch*> */ + git_pool *pool; + bool pool_is_allocated; } git_attr_file; typedef struct { + git_buf full; const char *path; const char *basename; - int is_dir; + int is_dir; } git_attr_path; /* * git_attr_file API */ -extern int git_attr_file__new(git_attr_file **attrs_ptr); +extern int git_attr_file__new(git_attr_file **attrs_ptr, git_pool *pool); extern void git_attr_file__free(git_attr_file *file); extern int git_attr_file__from_buffer( @@ -82,9 +85,9 @@ extern int git_attr_file__lookup_one( /* loop over rules in file from bottom to top */ #define git_attr_file__foreach_matching_rule(file, path, iter, rule) \ git_vector_rforeach(&(file)->rules, (iter), (rule)) \ - if (git_attr_rule__match((rule), (path)) == GIT_SUCCESS) + if (git_attr_rule__match((rule), (path))) -extern unsigned long git_attr_file__name_hash(const char *name); +extern uint32_t git_attr_file__name_hash(const char *name); /* @@ -93,16 +96,17 @@ extern unsigned long git_attr_file__name_hash(const char *name); extern int git_attr_fnmatch__parse( git_attr_fnmatch *spec, + git_pool *pool, const char *source, const char **base); -extern int git_attr_fnmatch__match( +extern bool git_attr_fnmatch__match( git_attr_fnmatch *rule, const git_attr_path *path); extern void git_attr_rule__free(git_attr_rule *rule); -extern int git_attr_rule__match( +extern bool git_attr_rule__match( git_attr_rule *rule, const git_attr_path *path); @@ -112,8 +116,11 @@ extern git_attr_assignment *git_attr_rule__lookup_assignment( extern int git_attr_path__init( git_attr_path *info, const char *path, const char *base); +extern void git_attr_path__free(git_attr_path *info); + extern int git_attr_assignment__parse( git_repository *repo, /* needed to expand macros */ + git_pool *pool, git_vector *assigns, const char **scan); diff --git a/src/blob.c b/src/blob.c index b67f8afa5..36571c70a 100644 --- a/src/blob.c +++ b/src/blob.c @@ -42,7 +42,7 @@ int git_blob__parse(git_blob *blob, git_odb_object *odb_obj) assert(blob); git_cached_obj_incref((git_cached_obj *)odb_obj); blob->odb_object = odb_obj; - return GIT_SUCCESS; + return 0; } int git_blob_create_frombuffer(git_oid *oid, git_repository *repo, const void *buffer, size_t len) @@ -51,58 +51,50 @@ int git_blob_create_frombuffer(git_oid *oid, git_repository *repo, const void *b git_odb *odb; git_odb_stream *stream; - error = git_repository_odb__weakptr(&odb, repo); - if (error < GIT_SUCCESS) + if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 || + (error = git_odb_open_wstream(&stream, odb, len, GIT_OBJ_BLOB)) < 0) return error; - if ((error = git_odb_open_wstream(&stream, odb, len, GIT_OBJ_BLOB)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to create blob"); + if ((error = stream->write(stream, buffer, len)) == 0) + error = stream->finalize_write(oid, stream); - if ((error = stream->write(stream, buffer, len)) < GIT_SUCCESS) { - stream->free(stream); - return error; - } - - error = stream->finalize_write(oid, stream); stream->free(stream); - - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to create blob"); - - return GIT_SUCCESS; + return error; } -static int write_file_stream(git_oid *oid, git_odb *odb, const char *path, git_off_t file_size) +static int write_file_stream( + git_oid *oid, git_odb *odb, const char *path, git_off_t file_size) { int fd, error; char buffer[4096]; git_odb_stream *stream = NULL; - if ((error = git_odb_open_wstream(&stream, odb, file_size, GIT_OBJ_BLOB)) < GIT_SUCCESS) + if ((error = git_odb_open_wstream( + &stream, odb, (size_t)file_size, GIT_OBJ_BLOB)) < 0) return error; - if ((fd = p_open(path, O_RDONLY)) < 0) { - error = git__throw(GIT_ENOTFOUND, "Failed to create blob. Could not open '%s'", path); - goto cleanup; + if ((fd = git_futils_open_ro(path)) < 0) { + stream->free(stream); + return -1; } - while (file_size > 0) { + while (!error && file_size > 0) { ssize_t read_len = p_read(fd, buffer, sizeof(buffer)); if (read_len < 0) { - error = git__throw(GIT_EOSERR, "Failed to create blob. Can't read full file"); - p_close(fd); - goto cleanup; + giterr_set( + GITERR_OS, "Failed to create blob. Can't read whole file"); + error = -1; } - - stream->write(stream, buffer, read_len); - file_size -= read_len; + else if (!(error = stream->write(stream, buffer, read_len))) + file_size -= read_len; } p_close(fd); - error = stream->finalize_write(oid, stream); -cleanup: + if (!error) + error = stream->finalize_write(oid, stream); + stream->free(stream); return error; } @@ -117,8 +109,7 @@ static int write_file_filtered( git_buf source = GIT_BUF_INIT; git_buf dest = GIT_BUF_INIT; - error = git_futils_readbuffer(&source, full_path); - if (error < GIT_SUCCESS) + if ((error = git_futils_readbuffer(&source, full_path)) < 0) return error; error = git_filters_apply(&dest, &source, filters); @@ -127,40 +118,39 @@ static int write_file_filtered( * and we don't want to ODB write to choke */ git_buf_free(&source); - if (error == GIT_SUCCESS) { - /* Write the file to disk if it was properly filtered */ + /* Write the file to disk if it was properly filtered */ + if (!error) error = git_odb_write(oid, odb, dest.ptr, dest.size, GIT_OBJ_BLOB); - } git_buf_free(&dest); - return GIT_SUCCESS; + return error; } -static int write_symlink(git_oid *oid, git_odb *odb, const char *path, size_t link_size) +static int write_symlink( + git_oid *oid, git_odb *odb, const char *path, size_t link_size) { char *link_data; ssize_t read_len; int error; link_data = git__malloc(link_size); - if (!link_data) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(link_data); read_len = p_readlink(path, link_data, link_size); - if (read_len != (ssize_t)link_size) { - free(link_data); - return git__throw(GIT_EOSERR, "Failed to create blob. Can't read symlink"); + giterr_set(GITERR_OS, "Failed to create blob. Can't read symlink '%s'", path); + git__free(link_data); + return -1; } error = git_odb_write(oid, odb, (void *)link_data, link_size, GIT_OBJ_BLOB); - free(link_data); + git__free(link_data); return error; } int git_blob_create_fromfile(git_oid *oid, git_repository *repo, const char *path) { - int error = GIT_SUCCESS; + int error; git_buf full_path = GIT_BUF_INIT; git_off_t size; struct stat st; @@ -168,25 +158,18 @@ int git_blob_create_fromfile(git_oid *oid, git_repository *repo, const char *pat git_odb *odb = NULL; workdir = git_repository_workdir(repo); - if (workdir == NULL) - return git__throw(GIT_ENOTFOUND, "Failed to create blob. (No working directory found)"); + assert(workdir); /* error to call this on bare repo */ - error = git_buf_joinpath(&full_path, workdir, path); - if (error < GIT_SUCCESS) + if ((error = git_buf_joinpath(&full_path, workdir, path)) < 0 || + (error = git_path_lstat(full_path.ptr, &st)) < 0 || + (error = git_repository_odb__weakptr(&odb, repo)) < 0) + { + git_buf_free(&full_path); return error; - - error = p_lstat(full_path.ptr, &st); - if (error < 0) { - error = git__throw(GIT_EOSERR, "Failed to stat blob. %s", strerror(errno)); - goto cleanup; } size = st.st_size; - error = git_repository_odb__weakptr(&odb, repo); - if (error < GIT_SUCCESS) - goto cleanup; - if (S_ISLNK(st.st_mode)) { error = write_symlink(oid, odb, full_path.ptr, (size_t)size); } else { @@ -194,12 +177,12 @@ int git_blob_create_fromfile(git_oid *oid, git_repository *repo, const char *pat int filter_count; /* Load the filters for writing this file to the ODB */ - filter_count = git_filters_load(&write_filters, repo, path, GIT_FILTER_TO_ODB); + filter_count = git_filters_load( + &write_filters, repo, path, GIT_FILTER_TO_ODB); if (filter_count < 0) { /* Negative value means there was a critical error */ error = filter_count; - goto cleanup; } else if (filter_count == 0) { /* No filters need to be applied to the document: we can stream * directly from disk */ @@ -212,19 +195,20 @@ int git_blob_create_fromfile(git_oid *oid, git_repository *repo, const char *pat git_filters_free(&write_filters); /* - * TODO: eventually support streaming filtered files, for files which are bigger - * than a given threshold. This is not a priority because applying a filter in - * streaming mode changes the final size of the blob, and without knowing its - * final size, the blob cannot be written in stream mode to the ODB. + * TODO: eventually support streaming filtered files, for files + * which are bigger than a given threshold. This is not a priority + * because applying a filter in streaming mode changes the final + * size of the blob, and without knowing its final size, the blob + * cannot be written in stream mode to the ODB. * - * The plan is to do streaming writes to a tempfile on disk and then opening - * streaming that file to the ODB, using `write_file_stream`. + * The plan is to do streaming writes to a tempfile on disk and then + * opening streaming that file to the ODB, using + * `write_file_stream`. * * CAREFULLY DESIGNED APIS YO */ } -cleanup: git_buf_free(&full_path); return error; } diff --git a/src/branch.c b/src/branch.c new file mode 100644 index 000000000..c980cf08c --- /dev/null +++ b/src/branch.c @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "commit.h" +#include "branch.h" +#include "tag.h" + +static int retrieve_branch_reference( + git_reference **branch_reference_out, + git_repository *repo, + const char *branch_name, + int is_remote) +{ + git_reference *branch; + int error = -1; + char *prefix; + git_buf ref_name = GIT_BUF_INIT; + + *branch_reference_out = NULL; + + prefix = is_remote ? GIT_REFS_REMOTES_DIR : GIT_REFS_HEADS_DIR; + + if (git_buf_joinpath(&ref_name, prefix, branch_name) < 0) + goto cleanup; + + if ((error = git_reference_lookup(&branch, repo, ref_name.ptr)) < 0) { + giterr_set(GITERR_REFERENCE, + "Cannot locate %s branch '%s'.", is_remote ? "remote-tracking" : "local", branch_name); + goto cleanup; + } + + *branch_reference_out = branch; + +cleanup: + git_buf_free(&ref_name); + return error; +} + +static int create_error_invalid(const char *msg) +{ + giterr_set(GITERR_INVALID, "Cannot create branch - %s", msg); + return -1; +} + +int git_branch_create( + git_oid *oid_out, + git_repository *repo, + const char *branch_name, + const git_object *target, + int force) +{ + git_otype target_type = GIT_OBJ_BAD; + git_object *commit = NULL; + git_reference *branch = NULL; + git_buf canonical_branch_name = GIT_BUF_INIT; + int error = -1; + + assert(repo && branch_name && target && oid_out); + + if (git_object_owner(target) != repo) + return create_error_invalid("The given target does not belong to this repository"); + + target_type = git_object_type(target); + + switch (target_type) + { + case GIT_OBJ_TAG: + if (git_tag_peel(&commit, (git_tag *)target) < 0) + goto cleanup; + + if (git_object_type(commit) != GIT_OBJ_COMMIT) { + create_error_invalid("The given target does not resolve to a commit"); + goto cleanup; + } + break; + + case GIT_OBJ_COMMIT: + commit = (git_object *)target; + break; + + default: + return create_error_invalid("Only git_tag and git_commit objects are valid targets."); + } + + if (git_buf_joinpath(&canonical_branch_name, GIT_REFS_HEADS_DIR, branch_name) < 0) + goto cleanup; + + if (git_reference_create_oid(&branch, repo, git_buf_cstr(&canonical_branch_name), git_object_id(commit), force) < 0) + goto cleanup; + + git_oid_cpy(oid_out, git_reference_oid(branch)); + error = 0; + +cleanup: + if (target_type == GIT_OBJ_TAG) + git_object_free(commit); + + git_reference_free(branch); + git_buf_free(&canonical_branch_name); + return error; +} + +int git_branch_delete(git_repository *repo, const char *branch_name, git_branch_type branch_type) +{ + git_reference *branch = NULL; + git_reference *head = NULL; + int error; + + assert((branch_type == GIT_BRANCH_LOCAL) || (branch_type == GIT_BRANCH_REMOTE)); + + if ((error = retrieve_branch_reference(&branch, repo, branch_name, branch_type == GIT_BRANCH_REMOTE)) < 0) + goto on_error; + + if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0) { + giterr_set(GITERR_REFERENCE, "Cannot locate HEAD."); + goto on_error; + } + + if ((git_reference_type(head) == GIT_REF_SYMBOLIC) + && (strcmp(git_reference_target(head), git_reference_name(branch)) == 0)) { + giterr_set(GITERR_REFERENCE, + "Cannot delete branch '%s' as it is the current HEAD of the repository.", branch_name); + goto on_error; + } + + if (git_reference_delete(branch) < 0) + goto on_error; + + git_reference_free(head); + return 0; + +on_error: + git_reference_free(head); + git_reference_free(branch); + return -1; +} + +typedef struct { + git_vector *branchlist; + unsigned int branch_type; +} branch_filter_data; + +static int branch_list_cb(const char *branch_name, void *payload) +{ + branch_filter_data *filter = (branch_filter_data *)payload; + + if ((filter->branch_type & GIT_BRANCH_LOCAL && git__prefixcmp(branch_name, GIT_REFS_HEADS_DIR) == 0) + || (filter->branch_type & GIT_BRANCH_REMOTE && git__prefixcmp(branch_name, GIT_REFS_REMOTES_DIR) == 0)) + return git_vector_insert(filter->branchlist, git__strdup(branch_name)); + + return 0; +} + +int git_branch_list(git_strarray *branch_names, git_repository *repo, unsigned int list_flags) +{ + int error; + branch_filter_data filter; + git_vector branchlist; + + assert(branch_names && repo); + + if (git_vector_init(&branchlist, 8, NULL) < 0) + return -1; + + filter.branchlist = &branchlist; + filter.branch_type = list_flags; + + error = git_reference_foreach(repo, GIT_REF_OID|GIT_REF_PACKED, &branch_list_cb, (void *)&filter); + if (error < 0) { + git_vector_free(&branchlist); + return -1; + } + + branch_names->strings = (char **)branchlist.contents; + branch_names->count = branchlist.length; + return 0; +} + +int git_branch_move(git_repository *repo, const char *old_branch_name, const char *new_branch_name, int force) +{ + git_reference *reference = NULL; + git_buf old_reference_name = GIT_BUF_INIT, new_reference_name = GIT_BUF_INIT; + int error = 0; + + if ((error = git_buf_joinpath(&old_reference_name, GIT_REFS_HEADS_DIR, old_branch_name)) < 0) + goto cleanup; + + /* We need to be able to return GIT_ENOTFOUND */ + if ((error = git_reference_lookup(&reference, repo, git_buf_cstr(&old_reference_name))) < 0) + goto cleanup; + + if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0) + goto cleanup; + + error = git_reference_rename(reference, git_buf_cstr(&new_reference_name), force); + +cleanup: + git_reference_free(reference); + git_buf_free(&old_reference_name); + git_buf_free(&new_reference_name); + + return error; +} diff --git a/src/branch.h b/src/branch.h new file mode 100644 index 000000000..d0e5abc8b --- /dev/null +++ b/src/branch.h @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_branch_h__ +#define INCLUDE_branch_h__ + +#include "git2/branch.h" + +struct git_branch { + char *remote; /* TODO: Make this a git_remote */ + char *merge; +}; + +#endif diff --git a/src/buffer.c b/src/buffer.c index 3098f6d68..24a0abdbe 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -17,8 +17,8 @@ char git_buf_initbuf[1]; static char git_buf__oom; #define ENSURE_SIZE(b, d) \ - if ((d) > buf->asize && git_buf_grow(b, (d)) < GIT_SUCCESS)\ - return GIT_ENOMEM; + if ((d) > buf->asize && git_buf_grow(b, (d)) < 0)\ + return -1; void git_buf_init(git_buf *buf, size_t initial_size) @@ -34,10 +34,8 @@ void git_buf_init(git_buf *buf, size_t initial_size) int git_buf_grow(git_buf *buf, size_t target_size) { int error = git_buf_try_grow(buf, target_size); - if (error != GIT_SUCCESS) { + if (error != 0) buf->ptr = &git_buf__oom; - } - return error; } @@ -47,10 +45,10 @@ int git_buf_try_grow(git_buf *buf, size_t target_size) size_t new_size; if (buf->ptr == &git_buf__oom) - return GIT_ENOMEM; + return -1; if (target_size <= buf->asize) - return GIT_SUCCESS; + return 0; if (buf->asize == 0) { new_size = target_size; @@ -70,7 +68,7 @@ int git_buf_try_grow(git_buf *buf, size_t target_size) new_ptr = git__realloc(new_ptr, new_size); if (!new_ptr) - return GIT_ENOMEM; + return -1; buf->asize = new_size; buf->ptr = new_ptr; @@ -80,7 +78,7 @@ int git_buf_try_grow(git_buf *buf, size_t target_size) buf->size = buf->asize - 1; buf->ptr[buf->size] = '\0'; - return GIT_SUCCESS; + return 0; } void git_buf_free(git_buf *buf) @@ -100,16 +98,11 @@ void git_buf_clear(git_buf *buf) buf->ptr[0] = '\0'; } -int git_buf_oom(const git_buf *buf) +bool git_buf_oom(const git_buf *buf) { return (buf->ptr == &git_buf__oom); } -int git_buf_lasterror(const git_buf *buf) -{ - return (buf->ptr == &git_buf__oom) ? GIT_ENOMEM : GIT_SUCCESS; -} - int git_buf_set(git_buf *buf, const char *data, size_t len) { if (len == 0 || data == NULL) { @@ -122,7 +115,7 @@ int git_buf_set(git_buf *buf, const char *data, size_t len) buf->size = len; buf->ptr[buf->size] = '\0'; } - return GIT_SUCCESS; + return 0; } int git_buf_sets(git_buf *buf, const char *string) @@ -135,7 +128,7 @@ int git_buf_putc(git_buf *buf, char c) ENSURE_SIZE(buf, buf->size + 2); buf->ptr[buf->size++] = c; buf->ptr[buf->size] = '\0'; - return GIT_SUCCESS; + return 0; } int git_buf_put(git_buf *buf, const char *data, size_t len) @@ -144,7 +137,7 @@ int git_buf_put(git_buf *buf, const char *data, size_t len) memmove(buf->ptr + buf->size, data, len); buf->size += len; buf->ptr[buf->size] = '\0'; - return GIT_SUCCESS; + return 0; } int git_buf_puts(git_buf *buf, const char *string) @@ -166,9 +159,9 @@ int git_buf_printf(git_buf *buf, const char *format, ...) va_end(arglist); if (len < 0) { - free(buf->ptr); + git__free(buf->ptr); buf->ptr = &git_buf__oom; - return GIT_ENOMEM; + return -1; } if ((size_t)len + 1 <= buf->asize - buf->size) { @@ -179,7 +172,7 @@ int git_buf_printf(git_buf *buf, const char *format, ...) ENSURE_SIZE(buf, buf->size + len + 1); } - return GIT_SUCCESS; + return 0; } void git_buf_copy_cstr(char *data, size_t datasize, const git_buf *buf) @@ -220,8 +213,8 @@ void git_buf_truncate(git_buf *buf, size_t len) void git_buf_rtruncate_at_char(git_buf *buf, char separator) { - int idx = git_buf_rfind_next(buf, separator); - git_buf_truncate(buf, idx < 0 ? 0 : idx); + ssize_t idx = git_buf_rfind_next(buf, separator); + git_buf_truncate(buf, idx < 0 ? 0 : (size_t)idx); } void git_buf_swap(git_buf *buf_a, git_buf *buf_b) @@ -262,9 +255,9 @@ void git_buf_attach(git_buf *buf, char *ptr, size_t asize) int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...) { va_list ap; - int i, error = GIT_SUCCESS; - size_t total_size = 0; - char *out; + int i; + size_t total_size = 0, original_size = buf->size; + char *out, *original = buf->ptr; if (buf->size > 0 && buf->ptr[buf->size - 1] != separator) ++total_size; /* space for initial separator */ @@ -288,9 +281,10 @@ int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...) va_end(ap); /* expand buffer if needed */ - if (total_size > 0 && - (error = git_buf_grow(buf, buf->size + total_size + 1)) < GIT_SUCCESS) - return error; + if (total_size == 0) + return 0; + if (git_buf_grow(buf, buf->size + total_size + 1) < 0) + return -1; out = buf->ptr + buf->size; @@ -307,12 +301,23 @@ int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...) if (!segment) continue; + /* deal with join that references buffer's original content */ + if (segment >= original && segment < original + original_size) { + size_t offset = (segment - original); + segment = buf->ptr + offset; + segment_len = original_size - offset; + } else { + segment_len = strlen(segment); + } + /* skip leading separators */ if (out > buf->ptr && out[-1] == separator) - while (*segment == separator) segment++; + while (segment_len > 0 && *segment == separator) { + segment++; + segment_len--; + } /* copy over next buffer */ - segment_len = strlen(segment); if (segment_len > 0) { memmove(out, segment, segment_len); out += segment_len; @@ -328,7 +333,7 @@ int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...) buf->size = out - buf->ptr; buf->ptr[buf->size] = '\0'; - return error; + return 0; } int git_buf_join( @@ -337,7 +342,6 @@ int git_buf_join( const char *str_a, const char *str_b) { - int error = GIT_SUCCESS; size_t strlen_a = str_a ? strlen(str_a) : 0; size_t strlen_b = strlen(str_b); int need_sep = 0; @@ -357,9 +361,8 @@ int git_buf_join( if (str_a >= buf->ptr && str_a < buf->ptr + buf->size) offset_a = str_a - buf->ptr; - error = git_buf_grow(buf, strlen_a + strlen_b + need_sep + 1); - if (error < GIT_SUCCESS) - return error; + if (git_buf_grow(buf, strlen_a + strlen_b + need_sep + 1) < 0) + return -1; /* fix up internal pointers */ if (offset_a >= 0) @@ -375,7 +378,7 @@ int git_buf_join( buf->size = strlen_a + strlen_b + need_sep; buf->ptr[buf->size] = '\0'; - return error; + return 0; } void git_buf_rtrim(git_buf *buf) diff --git a/src/buffer.h b/src/buffer.h index 3cdd794af..1cf588a62 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -32,7 +32,7 @@ void git_buf_init(git_buf *buf, size_t initial_size); * If the allocation fails, this will return an error and the buffer * will be marked as invalid for future operations. The existing * contents of the buffer will be preserved however. - * @return GIT_SUCCESS or GIT_ENOMEM on failure + * @return 0 on success or -1 on failure */ int git_buf_grow(git_buf *buf, size_t target_size); @@ -57,23 +57,18 @@ void git_buf_attach(git_buf *buf, char *ptr, size_t asize); * further calls to modify the buffer will fail. Check git_buf_oom() at the * end of your sequence and it will be true if you ran out of memory at any * point with that buffer. - * @return 0 if no error, 1 if allocation error. - */ -int git_buf_oom(const git_buf *buf); - -/** - * Just like git_buf_oom, except returns appropriate error code. - * @return GIT_ENOMEM if allocation error, GIT_SUCCESS if not. + * + * @return false if no error, true if allocation error */ -int git_buf_lasterror(const git_buf *buf); +bool git_buf_oom(const git_buf *buf); /* - * The functions below that return int values, will return GIT_ENOMEM - * if they fail to expand the git_buf when they are called, otherwise - * GIT_SUCCESS. Passing a git_buf that has failed an allocation will - * automatically return GIT_ENOMEM for all further calls. As a result, - * you can ignore the return code of these functions and call them in a - * series then just call git_buf_lasterror at the end. + * Functions below that return int value error codes will return 0 on + * success or -1 on failure (which generally means an allocation failed). + * Using a git_buf where the allocation has failed with result in -1 from + * all further calls using that buffer. As a result, you can ignore the + * return code of these functions and call them in a series then just call + * git_buf_oom at the end. */ int git_buf_set(git_buf *buf, const char *data, size_t len); int git_buf_sets(git_buf *buf, const char *string); @@ -91,25 +86,30 @@ int git_buf_join(git_buf *buf, char separator, const char *str_a, const char *st /** * Join two strings as paths, inserting a slash between as needed. - * @return error code or GIT_SUCCESS + * @return 0 on success, -1 on failure */ GIT_INLINE(int) git_buf_joinpath(git_buf *buf, const char *a, const char *b) { return git_buf_join(buf, '/', a, b); } -GIT_INLINE(const char *) git_buf_cstr(git_buf *buf) +GIT_INLINE(const char *) git_buf_cstr(const git_buf *buf) { return buf->ptr; } +GIT_INLINE(size_t) git_buf_len(const git_buf *buf) +{ + return buf->size; +} + void git_buf_copy_cstr(char *data, size_t datasize, const git_buf *buf); #define git_buf_PUTS(buf, str) git_buf_put(buf, str, sizeof(str) - 1) -GIT_INLINE(int) git_buf_rfind_next(git_buf *buf, char ch) +GIT_INLINE(ssize_t) git_buf_rfind_next(git_buf *buf, char ch) { - int idx = buf->size - 1; + ssize_t idx = (ssize_t)buf->size - 1; while (idx >= 0 && buf->ptr[idx] == ch) idx--; while (idx >= 0 && buf->ptr[idx] != ch) idx--; return idx; diff --git a/src/cache.c b/src/cache.c index 9e566792a..31da3c36e 100644 --- a/src/cache.c +++ b/src/cache.c @@ -9,21 +9,14 @@ #include "repository.h" #include "commit.h" #include "thread-utils.h" +#include "util.h" #include "cache.h" int git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_ptr) { if (size < 8) size = 8; - - /* round up size to closest power of 2 */ - size--; - size |= size >> 1; - size |= size >> 2; - size |= size >> 4; - size |= size >> 8; - size |= size >> 16; - size++; + size = git__size_t_powerof2(size); cache->size_mask = size - 1; cache->lru_count = 0; @@ -32,11 +25,10 @@ int git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_pt git_mutex_init(&cache->lock); cache->nodes = git__malloc(size * sizeof(git_cached_obj *)); - if (cache->nodes == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(cache->nodes); memset(cache->nodes, 0x0, size * sizeof(git_cached_obj *)); - return GIT_SUCCESS; + return 0; } void git_cache_free(git_cache *cache) diff --git a/src/cc-compat.h b/src/cc-compat.h index 3df36b61f..507985daa 100644 --- a/src/cc-compat.h +++ b/src/cc-compat.h @@ -50,4 +50,12 @@ # pragma warning ( disable : 4127 ) #endif +#if defined (_MSC_VER) + typedef unsigned char bool; +# define true 1 +# define false 0 +#else +# include <stdbool.h> +#endif + #endif /* INCLUDE_compat_h__ */ diff --git a/src/commit.c b/src/commit.c index 189d8fe9e..04f37fe16 100644 --- a/src/commit.c +++ b/src/commit.c @@ -69,24 +69,84 @@ int git_commit_create_v( ...) { va_list ap; - int i, error; + int i, res; const git_commit **parents; parents = git__malloc(parent_count * sizeof(git_commit *)); + GITERR_CHECK_ALLOC(parents); va_start(ap, parent_count); for (i = 0; i < parent_count; ++i) parents[i] = va_arg(ap, const git_commit *); va_end(ap); - error = git_commit_create( + res = git_commit_create( oid, repo, update_ref, author, committer, message_encoding, message, tree, parent_count, parents); git__free((void *)parents); + return res; +} + +/* Update the reference named `ref_name` so it points to `oid` */ +static int update_reference(git_repository *repo, git_oid *oid, const char *ref_name) +{ + git_reference *ref; + int res; + + res = git_reference_lookup(&ref, repo, ref_name); + + /* If we haven't found the reference at all, we assume we need to create + * a new reference and that's it */ + if (res == GIT_ENOTFOUND) { + giterr_clear(); + return git_reference_create_oid(NULL, repo, ref_name, oid, 1); + } + + if (res < 0) + return -1; + + /* If we have found a reference, but it's symbolic, we need to update + * the direct reference it points to */ + if (git_reference_type(ref) == GIT_REF_SYMBOLIC) { + git_reference *aux; + const char *sym_target; + + /* The target pointed at by this reference */ + sym_target = git_reference_target(ref); + + /* resolve the reference to the target it points to */ + res = git_reference_resolve(&aux, ref); - return error; + /* + * if the symbolic reference pointed to an inexisting ref, + * this is means we're creating a new branch, for example. + * We need to create a new direct reference with that name + */ + if (res == GIT_ENOTFOUND) { + giterr_clear(); + res = git_reference_create_oid(NULL, repo, sym_target, oid, 1); + git_reference_free(ref); + return res; + } + + /* free the original symbolic reference now; not before because + * we're using the `sym_target` pointer */ + git_reference_free(ref); + + if (res < 0) + return -1; + + /* store the newly found direct reference in its place */ + ref = aux; + } + + /* ref is made to point to `oid`: ref is either the original reference, + * or the target of the symbolic reference we've looked up */ + res = git_reference_set_oid(ref, oid); + git_reference_free(ref); + return res; } int git_commit_create( @@ -102,20 +162,15 @@ int git_commit_create( const git_commit *parents[]) { git_buf commit = GIT_BUF_INIT; - int error, i; + int i; git_odb *odb; - if (git_object_owner((const git_object *)tree) != repo) - return git__throw(GIT_EINVALIDARGS, "The given tree does not belong to this repository"); + assert(git_object_owner((const git_object *)tree) == repo); git_oid__writebuf(&commit, "tree ", git_object_id((const git_object *)tree)); for (i = 0; i < parent_count; ++i) { - if (git_object_owner((const git_object *)parents[i]) != repo) { - error = git__throw(GIT_EINVALIDARGS, "The given parent does not belong to this repository"); - goto cleanup; - } - + assert(git_object_owner((const git_object *)parents[i]) == repo); git_oid__writebuf(&commit, "parent ", git_object_id((const git_object *)parents[i])); } @@ -128,67 +183,25 @@ int git_commit_create( git_buf_putc(&commit, '\n'); git_buf_puts(&commit, message); - if (git_buf_oom(&commit)) { - error = git__throw(git_buf_lasterror(&commit), - "Not enough memory to build the commit data"); - goto cleanup; - } - - error = git_repository_odb__weakptr(&odb, repo); - if (error < GIT_SUCCESS) - goto cleanup; - - error = git_odb_write(oid, odb, commit.ptr, commit.size, GIT_OBJ_COMMIT); - git_buf_free(&commit); - - if (error == GIT_SUCCESS && update_ref != NULL) { - git_reference *head; - git_reference *target; - - error = git_reference_lookup(&head, repo, update_ref); - if (error < GIT_SUCCESS && error != GIT_ENOTFOUND) - return git__rethrow(error, "Failed to create commit"); - - if (error != GIT_ENOTFOUND) { - update_ref = git_reference_target(head); - error = git_reference_resolve(&target, head); - } - - if (error < GIT_SUCCESS) { - if (error != GIT_ENOTFOUND) { - git_reference_free(head); - return git__rethrow(error, "Failed to create commit"); - } - /* - * The target of the reference was not found. This can happen - * just after a repository has been initialized (the master - * branch doesn't exist yet, as it doesn't have anything to - * point to) or after an orphan checkout, so if the target - * branch doesn't exist yet, create it and return. - */ - error = git_reference_create_oid(&target, repo, update_ref, oid, 1); - - git_reference_free(head); - if (error == GIT_SUCCESS) - git_reference_free(target); + if (git_buf_oom(&commit)) + goto on_error; - return error; - } + if (git_repository_odb__weakptr(&odb, repo) < 0) + goto on_error; - error = git_reference_set_oid(target, oid); + if (git_odb_write(oid, odb, commit.ptr, commit.size, GIT_OBJ_COMMIT) < 0) + goto on_error; - git_reference_free(head); - git_reference_free(target); - } + git_buf_free(&commit); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to create commit"); + if (update_ref != NULL) + return update_reference(repo, oid, update_ref); - return GIT_SUCCESS; + return 0; -cleanup: +on_error: git_buf_free(&commit); - return error; + return -1; } int git_commit__parse_buffer(git_commit *commit, const void *data, size_t len) @@ -197,35 +210,40 @@ int git_commit__parse_buffer(git_commit *commit, const void *data, size_t len) const char *buffer_end = (const char *)data + len; git_oid parent_oid; - int error; git_vector_init(&commit->parent_oids, 4, NULL); - if ((error = git_oid__parse(&commit->tree_oid, &buffer, buffer_end, "tree ")) < GIT_SUCCESS) - return git__rethrow(error, "Failed to parse buffer"); + if (git_oid__parse(&commit->tree_oid, &buffer, buffer_end, "tree ") < 0) + goto bad_buffer; /* * TODO: commit grafts! */ - while (git_oid__parse(&parent_oid, &buffer, buffer_end, "parent ") == GIT_SUCCESS) { + while (git_oid__parse(&parent_oid, &buffer, buffer_end, "parent ") == 0) { git_oid *new_oid; new_oid = git__malloc(sizeof(git_oid)); + GITERR_CHECK_ALLOC(new_oid); + git_oid_cpy(new_oid, &parent_oid); - if (git_vector_insert(&commit->parent_oids, new_oid) < GIT_SUCCESS) - return GIT_ENOMEM; + if (git_vector_insert(&commit->parent_oids, new_oid) < 0) + return -1; } commit->author = git__malloc(sizeof(git_signature)); - if ((error = git_signature__parse(commit->author, &buffer, buffer_end, "author ", '\n')) < GIT_SUCCESS) - return git__rethrow(error, "Failed to parse commit"); + GITERR_CHECK_ALLOC(commit->author); + + if (git_signature__parse(commit->author, &buffer, buffer_end, "author ", '\n') < 0) + return -1; /* Always parse the committer; we need the commit time */ commit->committer = git__malloc(sizeof(git_signature)); - if ((error = git_signature__parse(commit->committer, &buffer, buffer_end, "committer ", '\n')) < GIT_SUCCESS) - return git__rethrow(error, "Failed to parse commit"); + GITERR_CHECK_ALLOC(commit->committer); + + if (git_signature__parse(commit->committer, &buffer, buffer_end, "committer ", '\n') < 0) + return -1; if (git__prefixcmp(buffer, "encoding ") == 0) { const char *encoding_end; @@ -236,8 +254,7 @@ int git_commit__parse_buffer(git_commit *commit, const void *data, size_t len) encoding_end++; commit->message_encoding = git__strndup(buffer, encoding_end - buffer); - if (!commit->message_encoding) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(commit->message_encoding); buffer = encoding_end; } @@ -248,11 +265,14 @@ int git_commit__parse_buffer(git_commit *commit, const void *data, size_t len) if (buffer <= buffer_end) { commit->message = git__strndup(buffer, buffer_end - buffer); - if (!commit->message) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(commit->message); } - return GIT_SUCCESS; + return 0; + +bad_buffer: + giterr_set(GITERR_OBJECT, "Failed to parse bad commit object"); + return -1; } int git_commit__parse(git_commit *commit, git_odb_object *obj) @@ -290,8 +310,10 @@ int git_commit_parent(git_commit **parent, git_commit *commit, unsigned int n) assert(commit); parent_oid = git_vector_get(&commit->parent_oids, n); - if (parent_oid == NULL) - return git__throw(GIT_ENOTFOUND, "Parent %u does not exist", n); + if (parent_oid == NULL) { + giterr_set(GITERR_INVALID, "Parent %u does not exist", n); + return GIT_ENOTFOUND; + } return git_commit_lookup(parent, commit->object.repo, parent_oid); } diff --git a/src/common.h b/src/common.h index 0b4dc2e49..30757de70 100644 --- a/src/common.h +++ b/src/common.h @@ -46,6 +46,8 @@ #include "thread-utils.h" #include "bswap.h" +#include <regex.h> + extern void git___throw(const char *, ...) GIT_FORMAT_PRINTF(1, 2); #define git__throw(error, ...) \ (git___throw(__VA_ARGS__), error) @@ -54,6 +56,16 @@ extern void git___rethrow(const char *, ...) GIT_FORMAT_PRINTF(1, 2); #define git__rethrow(error, ...) \ (git___rethrow(__VA_ARGS__), error) + +#define GITERR_CHECK_ALLOC(ptr) if (ptr == NULL) { return -1; } + +void giterr_set_oom(void); +void giterr_set(int error_class, const char *string, ...); +void giterr_clear(void); +void giterr_set_str(int error_class, const char *string); +void giterr_set_regex(const regex_t *regex, int error_code); + + #include "util.h" diff --git a/src/config.c b/src/config.c index 912224158..4c971924c 100644 --- a/src/config.c +++ b/src/config.c @@ -7,7 +7,6 @@ #include "common.h" #include "fileops.h" -#include "hashtable.h" #include "config.h" #include "git2/config.h" #include "vector.h" @@ -67,77 +66,71 @@ int git_config_new(git_config **out) if (git_vector_init(&cfg->files, 3, config_backend_cmp) < 0) { git__free(cfg); - return GIT_ENOMEM; + return -1; } *out = cfg; GIT_REFCOUNT_INC(cfg); - return GIT_SUCCESS; + return 0; } int git_config_add_file_ondisk(git_config *cfg, const char *path, int priority) { git_config_file *file = NULL; - int error; - error = git_config_file__ondisk(&file, path); - if (error < GIT_SUCCESS) - return error; + if (git_config_file__ondisk(&file, path) < 0) + return -1; - error = git_config_add_file(cfg, file, priority); - if (error < GIT_SUCCESS) { + if (git_config_add_file(cfg, file, priority) < 0) { /* * free manually; the file is not owned by the config * instance yet and will not be freed on cleanup */ file->free(file); - return error; + return -1; } - return GIT_SUCCESS; + return 0; } int git_config_open_ondisk(git_config **cfg, const char *path) { - int error; - - error = git_config_new(cfg); - if (error < GIT_SUCCESS) - return error; + if (git_config_new(cfg) < 0) + return -1; - error = git_config_add_file_ondisk(*cfg, path, 1); - if (error < GIT_SUCCESS) + if (git_config_add_file_ondisk(*cfg, path, 1) < 0) { git_config_free(*cfg); + return -1; + } - return error; + return 0; } int git_config_add_file(git_config *cfg, git_config_file *file, int priority) { file_internal *internal; - int error; + int result; assert(cfg && file); - if ((error = file->open(file)) < GIT_SUCCESS) - return git__throw(error, "Failed to open config file"); + if ((result = file->open(file)) < 0) + return result; internal = git__malloc(sizeof(file_internal)); - if (internal == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(internal); internal->file = file; internal->priority = priority; if (git_vector_insert(&cfg->files, internal) < 0) { git__free(internal); - return GIT_ENOMEM; + return -1; } git_vector_sort(&cfg->files); internal->file->cfg = cfg; - return GIT_SUCCESS; + return 0; } /* @@ -146,7 +139,7 @@ int git_config_add_file(git_config *cfg, git_config_file *file, int priority) int git_config_foreach(git_config *cfg, int (*fn)(const char *, const char *, void *), void *data) { - int ret = GIT_SUCCESS; + int ret = 0; unsigned int i; file_internal *internal; git_config_file *file; @@ -165,8 +158,7 @@ int git_config_delete(git_config *cfg, const char *name) file_internal *internal; git_config_file *file; - if (cfg->files.length == 0) - return git__throw(GIT_EINVALIDARGS, "Cannot delete variable; no files open in the `git_config` instance"); + assert(cfg->files.length); internal = git_vector_get(&cfg->files, 0); file = internal->file; @@ -200,8 +192,7 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value) file_internal *internal; git_config_file *file; - if (cfg->files.length == 0) - return git__throw(GIT_EINVALIDARGS, "Cannot set variable value; no files open in the `git_config` instance"); + assert(cfg->files.length); internal = git_vector_get(&cfg->files, 0); file = internal->file; @@ -209,25 +200,25 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value) return file->set(file, name, value); } -static int parse_bool(int *out, const char *value) +int git_config_parse_bool(int *out, const char *value) { /* A missing value means true */ if (value == NULL) { *out = 1; - return GIT_SUCCESS; + return 0; } if (!strcasecmp(value, "true") || !strcasecmp(value, "yes") || !strcasecmp(value, "on")) { *out = 1; - return GIT_SUCCESS; + return 0; } if (!strcasecmp(value, "false") || !strcasecmp(value, "no") || !strcasecmp(value, "off")) { *out = 0; - return GIT_SUCCESS; + return 0; } return GIT_EINVALIDTYPE; @@ -239,7 +230,7 @@ static int parse_int64(int64_t *out, const char *value) int64_t num; if (git__strtol64(&num, value, &num_end, 0) < 0) - return GIT_EINVALIDTYPE; + return -1; switch (*num_end) { case 'g': @@ -259,7 +250,7 @@ static int parse_int64(int64_t *out, const char *value) /* check that that there are no more characters after the * given modifier suffix */ if (num_end[1] != '\0') - return GIT_EINVALIDTYPE; + return -1; /* fallthrough */ @@ -268,7 +259,7 @@ static int parse_int64(int64_t *out, const char *value) return 0; default: - return GIT_EINVALIDTYPE; + return -1; } } @@ -278,11 +269,11 @@ static int parse_int32(int32_t *out, const char *value) int32_t truncate; if (parse_int64(&tmp, value) < 0) - return GIT_EINVALIDTYPE; + return -1; truncate = tmp & 0xFFFFFFFF; if (truncate != tmp) - return GIT_EOVERFLOW; + return -1; *out = truncate; return 0; @@ -291,49 +282,62 @@ static int parse_int32(int32_t *out, const char *value) /*********** * Getters ***********/ -int git_config_get_mapped(git_config *cfg, const char *name, git_cvar_map *maps, size_t map_n, int *out) +int git_config_lookup_map_value( + git_cvar_map *maps, size_t map_n, const char *value, int *out) { size_t i; - const char *value; - int error; - error = git_config_get_string(cfg, name, &value); - if (error < GIT_SUCCESS) - return error; + if (!value) + return GIT_ENOTFOUND; for (i = 0; i < map_n; ++i) { git_cvar_map *m = maps + i; switch (m->cvar_type) { - case GIT_CVAR_FALSE: - case GIT_CVAR_TRUE: { - int bool_val; + case GIT_CVAR_FALSE: + case GIT_CVAR_TRUE: { + int bool_val; + + if (git_config_parse_bool(&bool_val, value) == 0 && + bool_val == (int)m->cvar_type) { + *out = m->map_value; + return 0; + } + break; + } - if (parse_bool(&bool_val, value) == 0 && - bool_val == (int)m->cvar_type) { - *out = m->map_value; - return 0; - } + case GIT_CVAR_INT32: + if (parse_int32(out, value) == 0) + return 0; + break; - break; + case GIT_CVAR_STRING: + if (strcasecmp(value, m->str_match) == 0) { + *out = m->map_value; + return 0; } + break; + } + } - case GIT_CVAR_INT32: - if (parse_int32(out, value) == 0) - return 0; + return GIT_ENOTFOUND; +} - break; +int git_config_get_mapped(git_config *cfg, const char *name, git_cvar_map *maps, size_t map_n, int *out) +{ + const char *value; + int ret; - case GIT_CVAR_STRING: - if (strcasecmp(value, m->str_match) == 0) { - *out = m->map_value; - return 0; - } - } - } + ret = git_config_get_string(cfg, name, &value); + if (ret < 0) + return ret; + + if (!git_config_lookup_map_value(maps, map_n, value, out)) + return 0; - return git__throw(GIT_ENOTFOUND, + giterr_set(GITERR_CONFIG, "Failed to map the '%s' config variable with a valid value", name); + return -1; } int git_config_get_int64(git_config *cfg, const char *name, int64_t *out) @@ -342,69 +346,73 @@ int git_config_get_int64(git_config *cfg, const char *name, int64_t *out) int ret; ret = git_config_get_string(cfg, name, &value); - if (ret < GIT_SUCCESS) - return git__rethrow(ret, "Failed to retrieve value for '%s'", name); + if (ret < 0) + return ret; - if (parse_int64(out, value) < 0) - return git__throw(GIT_EINVALIDTYPE, "Failed to parse '%s' as an integer", value); + if (parse_int64(out, value) < 0) { + giterr_set(GITERR_CONFIG, "Failed to parse '%s' as an integer", value); + return -1; + } - return GIT_SUCCESS; + return 0; } int git_config_get_int32(git_config *cfg, const char *name, int32_t *out) { const char *value; - int error; + int ret; - error = git_config_get_string(cfg, name, &value); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to get value for %s", name); + ret = git_config_get_string(cfg, name, &value); + if (ret < 0) + return ret; + + if (parse_int32(out, value) < 0) { + giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a 32-bit integer", value); + return -1; + } - error = parse_int32(out, value); - if (error < GIT_SUCCESS) - return git__throw(GIT_EINVALIDTYPE, "Failed to parse '%s' as a 32-bit integer", value); - - return GIT_SUCCESS; + return 0; } int git_config_get_bool(git_config *cfg, const char *name, int *out) { const char *value; - int error = GIT_SUCCESS; + int ret; - error = git_config_get_string(cfg, name, &value); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to get value for %s", name); + ret = git_config_get_string(cfg, name, &value); + if (ret < 0) + return ret; - if (parse_bool(out, value) == 0) - return GIT_SUCCESS; + if (git_config_parse_bool(out, value) == 0) + return 0; if (parse_int32(out, value) == 0) { *out = !!(*out); - return GIT_SUCCESS; + return 0; } - return git__throw(GIT_EINVALIDTYPE, "Failed to parse '%s' as a boolean value", value); + giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a boolean value", value); + return -1; } int git_config_get_string(git_config *cfg, const char *name, const char **out) { file_internal *internal; - git_config_file *file; - int error = GIT_ENOTFOUND; unsigned int i; - if (cfg->files.length == 0) - return git__throw(GIT_EINVALIDARGS, "Cannot get variable value; no files open in the `git_config` instance"); + assert(cfg->files.length); - for (i = 0; i < cfg->files.length; ++i) { - internal = git_vector_get(&cfg->files, i); - file = internal->file; - if ((error = file->get(file, name, out)) == GIT_SUCCESS) - return GIT_SUCCESS; + *out = NULL; + + git_vector_foreach(&cfg->files, i, internal) { + git_config_file *file = internal->file; + int ret = file->get(file, name, out); + if (ret != GIT_ENOTFOUND) + return ret; } - return git__throw(error, "Config value '%s' not found", name); + giterr_set(GITERR_CONFIG, "Config variable '%s' not found", name); + return GIT_ENOTFOUND; } int git_config_get_multivar(git_config *cfg, const char *name, const char *regexp, @@ -412,12 +420,10 @@ int git_config_get_multivar(git_config *cfg, const char *name, const char *regex { file_internal *internal; git_config_file *file; - int error = GIT_ENOTFOUND; + int ret = GIT_ENOTFOUND; unsigned int i; - - if (cfg->files.length == 0) - return git__throw(GIT_EINVALIDARGS, "Cannot get variable value; no files open in the `git_config` instance"); + assert(cfg->files.length); /* * This loop runs the "wrong" way 'round because we need to @@ -426,30 +432,30 @@ int git_config_get_multivar(git_config *cfg, const char *name, const char *regex for (i = cfg->files.length; i > 0; --i) { internal = git_vector_get(&cfg->files, i - 1); file = internal->file; - error = file->get_multivar(file, name, regexp, fn, data); - if (error < GIT_SUCCESS && error != GIT_ENOTFOUND) - git__rethrow(error, "Failed to get multivar"); + ret = file->get_multivar(file, name, regexp, fn, data); + if (ret < 0 && ret != GIT_ENOTFOUND) + return ret; } - return GIT_SUCCESS; + return 0; } int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value) { file_internal *internal; git_config_file *file; - int error = GIT_ENOTFOUND; + int ret = GIT_ENOTFOUND; unsigned int i; for (i = cfg->files.length; i > 0; --i) { internal = git_vector_get(&cfg->files, i - 1); file = internal->file; - error = file->set_multivar(file, name, regexp, value); - if (error < GIT_SUCCESS && error != GIT_ENOTFOUND) - git__rethrow(error, "Failed to replace multivar"); + ret = file->set_multivar(file, name, regexp, value); + if (ret < 0 && ret != GIT_ENOTFOUND) + return ret; } - return GIT_SUCCESS; + return 0; } int git_config_find_global_r(git_buf *path) @@ -460,18 +466,23 @@ int git_config_find_global_r(git_buf *path) int git_config_find_global(char *global_config_path) { git_buf path = GIT_BUF_INIT; - int error = git_config_find_global_r(&path); + int ret = git_config_find_global_r(&path); - if (error == GIT_SUCCESS) { - if (path.size > GIT_PATH_MAX) - error = git__throw(GIT_ESHORTBUFFER, "Path is too long"); - else - git_buf_copy_cstr(global_config_path, GIT_PATH_MAX, &path); + if (ret < 0) { + git_buf_free(&path); + return ret; } - git_buf_free(&path); + if (path.size > GIT_PATH_MAX) { + git_buf_free(&path); + giterr_set(GITERR_NOMEMORY, + "Path is to long to fit on the given buffer"); + return -1; + } - return error; + git_buf_copy_cstr(global_config_path, GIT_PATH_MAX, &path); + git_buf_free(&path); + return 0; } int git_config_find_system_r(git_buf *path) @@ -482,18 +493,23 @@ int git_config_find_system_r(git_buf *path) int git_config_find_system(char *system_config_path) { git_buf path = GIT_BUF_INIT; - int error = git_config_find_system_r(&path); + int ret = git_config_find_system_r(&path); - if (error == GIT_SUCCESS) { - if (path.size > GIT_PATH_MAX) - error = git__throw(GIT_ESHORTBUFFER, "Path is too long"); - else - git_buf_copy_cstr(system_config_path, GIT_PATH_MAX, &path); + if (ret < 0) { + git_buf_free(&path); + return ret; } - git_buf_free(&path); + if (path.size > GIT_PATH_MAX) { + git_buf_free(&path); + giterr_set(GITERR_NOMEMORY, + "Path is to long to fit on the given buffer"); + return -1; + } - return error; + git_buf_copy_cstr(system_config_path, GIT_PATH_MAX, &path); + git_buf_free(&path); + return 0; } int git_config_open_global(git_config **out) @@ -501,7 +517,7 @@ int git_config_open_global(git_config **out) int error; char global_path[GIT_PATH_MAX]; - if ((error = git_config_find_global(global_path)) < GIT_SUCCESS) + if ((error = git_config_find_global(global_path)) < 0) return error; return git_config_open_ondisk(out, global_path); diff --git a/src/config.h b/src/config.h index 59d1d9a26..82e98ce51 100644 --- a/src/config.h +++ b/src/config.h @@ -25,4 +25,9 @@ struct git_config { extern int git_config_find_global_r(git_buf *global_config_path); extern int git_config_find_system_r(git_buf *system_config_path); +extern int git_config_parse_bool(int *out, const char *bool_string); + +extern int git_config_lookup_map_value( + git_cvar_map *maps, size_t map_n, const char *value, int *out); + #endif diff --git a/src/config_cache.c b/src/config_cache.c index 5e20847f5..3679a9646 100644 --- a/src/config_cache.c +++ b/src/config_cache.c @@ -7,7 +7,6 @@ #include "common.h" #include "fileops.h" -#include "hashtable.h" #include "config.h" #include "git2/config.h" #include "vector.h" diff --git a/src/config_file.c b/src/config_file.c index e1f4ef932..ed5caf980 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -12,12 +12,14 @@ #include "buffer.h" #include "git2/config.h" #include "git2/types.h" - +#include "strmap.h" #include <ctype.h> #include <sys/types.h> #include <regex.h> +GIT__USE_STRMAP; + typedef struct cvar_t { struct cvar_t *next; char *key; /* TODO: we might be able to get rid of this */ @@ -70,7 +72,7 @@ typedef struct { typedef struct { git_config_file parent; - git_hashtable *values; + git_strmap *values; struct { git_buf buffer; @@ -86,6 +88,12 @@ static int config_parse(diskfile_backend *cfg_file); static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_value); static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char *value); +static void set_parse_error(diskfile_backend *backend, int col, const char *error_str) +{ + giterr_set(GITERR_CONFIG, "Failed to parse config file: %s (in %s:%d, column %d)", + error_str, backend->file_path, backend->reader.line_number, col); +} + static void cvar_free(cvar_t *var) { if (var == NULL) @@ -104,15 +112,16 @@ static int normalize_name(const char *in, char **out) assert(in && out); name = git__strdup(in); - if (name == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(name); fdot = strchr(name, '.'); ldot = strrchr(name, '.'); if (fdot == NULL || ldot == NULL) { git__free(name); - return git__throw(GIT_EINVALIDARGS, "Bad format. No dot in '%s'", in); + giterr_set(GITERR_CONFIG, + "Invalid variable name: '%s'", in); + return -1; } /* Downcase up to the first dot and after the last one */ @@ -120,60 +129,50 @@ static int normalize_name(const char *in, char **out) git__strtolower(ldot); *out = name; - return GIT_SUCCESS; + return 0; } -static void free_vars(git_hashtable *values) +static void free_vars(git_strmap *values) { cvar_t *var = NULL; if (values == NULL) return; - GIT_HASHTABLE_FOREACH_VALUE(values, var, - do { - cvar_t *next = CVAR_LIST_NEXT(var); - cvar_free(var); - var = next; - } while (var != NULL); - ) + git_strmap_foreach_value(values, var, + while (var != NULL) { + cvar_t *next = CVAR_LIST_NEXT(var); + cvar_free(var); + var = next; + }); - git_hashtable_free(values); + git_strmap_free(values); } static int config_open(git_config_file *cfg) { - int error; + int res; diskfile_backend *b = (diskfile_backend *)cfg; - b->values = git_hashtable_alloc (20, git_hash__strhash_cb, git_hash__strcmp_cb); - if (b->values == NULL) - return GIT_ENOMEM; + b->values = git_strmap_alloc(); + GITERR_CHECK_ALLOC(b->values); git_buf_init(&b->reader.buffer, 0); - error = git_futils_readbuffer(&b->reader.buffer, b->file_path); + res = git_futils_readbuffer(&b->reader.buffer, b->file_path); /* It's fine if the file doesn't exist */ - if (error == GIT_ENOTFOUND) - return GIT_SUCCESS; - - if (error < GIT_SUCCESS) - goto cleanup; - - error = config_parse(b); - if (error < GIT_SUCCESS) - goto cleanup; - - git_buf_free(&b->reader.buffer); - - return GIT_SUCCESS; + if (res == GIT_ENOTFOUND) + return 0; + + if (res < 0 || config_parse(b) < 0) { + free_vars(b->values); + b->values = NULL; + git_buf_free(&b->reader.buffer); + return -1; + } - cleanup: - free_vars(b->values); - b->values = NULL; git_buf_free(&b->reader.buffer); - - return git__rethrow(error, "Failed to open config"); + return 0; } static void backend_free(git_config_file *_backend) @@ -184,58 +183,61 @@ static void backend_free(git_config_file *_backend) return; git__free(backend->file_path); - free_vars(backend->values); - git__free(backend); } static int file_foreach(git_config_file *backend, int (*fn)(const char *, const char *, void *), void *data) { - int ret = GIT_SUCCESS; - cvar_t *var; diskfile_backend *b = (diskfile_backend *)backend; + cvar_t *var; const char *key; - GIT_HASHTABLE_FOREACH(b->values, key, var, - do { - ret = fn(key, var->value, data); - if (ret) - break; - var = CVAR_LIST_NEXT(var); - } while (var != NULL); - ) + if (!b->values) + return 0; + git_strmap_foreach(b->values, key, var, + do { + if (fn(key, var->value, data) < 0) + break; - return ret; + var = CVAR_LIST_NEXT(var); + } while (var != NULL); + ); + + return 0; } static int config_set(git_config_file *cfg, const char *name, const char *value) { - cvar_t *var = NULL; - cvar_t *existing = NULL, *old_value = NULL; - int error = GIT_SUCCESS; + cvar_t *var = NULL, *old_var; diskfile_backend *b = (diskfile_backend *)cfg; char *key; + khiter_t pos; + int rval; - if ((error = normalize_name(name, &key)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to normalize variable name '%s'", name); + if (normalize_name(name, &key) < 0) + return -1; /* * Try to find it in the existing values and update it if it * only has one value. */ - existing = git_hashtable_lookup(b->values, key); - if (existing != NULL) { - char *tmp; + pos = git_strmap_lookup_index(b->values, key); + if (git_strmap_valid_index(b->values, pos)) { + cvar_t *existing = git_strmap_value_at(b->values, pos); + char *tmp = NULL; git__free(key); - if (existing->next != NULL) - return git__throw(GIT_EINVALIDARGS, "Multivar incompatible with simple set"); + if (existing->next != NULL) { + giterr_set(GITERR_CONFIG, "Multivar incompatible with simple set"); + return -1; + } - tmp = value ? git__strdup(value) : NULL; - if (tmp == NULL && value != NULL) - return GIT_ENOMEM; + if (value) { + tmp = git__strdup(value); + GITERR_CHECK_ALLOC(tmp); + } git__free(existing->value); existing->value = tmp; @@ -244,32 +246,30 @@ static int config_set(git_config_file *cfg, const char *name, const char *value) } var = git__malloc(sizeof(cvar_t)); - if (var == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(var); memset(var, 0x0, sizeof(cvar_t)); var->key = key; + var->value = NULL; - var->value = value ? git__strdup(value) : NULL; - if (var->value == NULL && value != NULL) { - error = GIT_ENOMEM; - goto out; + if (value) { + var->value = git__strdup(value); + GITERR_CHECK_ALLOC(var->value); } - error = git_hashtable_insert2(b->values, key, var, (void **)&old_value); - if (error < GIT_SUCCESS) - goto out; - - cvar_free(old_value); - - error = config_write(b, key, NULL, value); + git_strmap_insert2(b->values, key, var, old_var, rval); + if (rval < 0) + return -1; + if (old_var != NULL) + cvar_free(old_var); - out: - if (error < GIT_SUCCESS) + if (config_write(b, key, NULL, value) < 0) { cvar_free(var); + return -1; + } - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to set config value"); + return 0; } /* @@ -277,172 +277,188 @@ static int config_set(git_config_file *cfg, const char *name, const char *value) */ static int config_get(git_config_file *cfg, const char *name, const char **out) { - cvar_t *var; - int error = GIT_SUCCESS; diskfile_backend *b = (diskfile_backend *)cfg; char *key; + khiter_t pos; - if ((error = normalize_name(name, &key)) < GIT_SUCCESS) - return error; + if (normalize_name(name, &key) < 0) + return -1; - var = git_hashtable_lookup(b->values, key); + pos = git_strmap_lookup_index(b->values, key); git__free(key); - if (var == NULL) - return git__throw(GIT_ENOTFOUND, "Variable '%s' not found", name); + /* no error message; the config system will write one */ + if (!git_strmap_valid_index(b->values, pos)) + return GIT_ENOTFOUND; - *out = var->value; + *out = ((cvar_t *)git_strmap_value_at(b->values, pos))->value; - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to get config value for %s", name); + return 0; } -static int config_get_multivar(git_config_file *cfg, const char *name, const char *regexp, int (*fn)(const char *, void *), void *data) +static int config_get_multivar( + git_config_file *cfg, + const char *name, + const char *regex_str, + int (*fn)(const char *, void *), + void *data) { cvar_t *var; - int error = GIT_SUCCESS; diskfile_backend *b = (diskfile_backend *)cfg; char *key; - regex_t preg; + khiter_t pos; - if ((error = normalize_name(name, &key)) < GIT_SUCCESS) - return error; + if (normalize_name(name, &key) < 0) + return -1; - var = git_hashtable_lookup(b->values, key); + pos = git_strmap_lookup_index(b->values, key); git__free(key); - if (var == NULL) - return git__throw(GIT_ENOTFOUND, "Variable '%s' not found", name); + if (!git_strmap_valid_index(b->values, pos)) + return GIT_ENOTFOUND; - if (regexp != NULL) { - error = regcomp(&preg, regexp, REG_EXTENDED); - if (error < 0) - return git__throw(GIT_EINVALIDARGS, "Failed to compile regex"); - } + var = git_strmap_value_at(b->values, pos); - do { - if (regexp == NULL || !regexec(&preg, var->value, 0, NULL, 0)) { - error = fn(var->value, data); - if (error < GIT_SUCCESS) - goto exit; + if (regex_str != NULL) { + regex_t regex; + int result; + + /* regex matching; build the regex */ + result = regcomp(®ex, regex_str, REG_EXTENDED); + if (result < 0) { + giterr_set_regex(®ex, result); + return -1; } - var = var->next; - } while (var != NULL); + /* and throw the callback only on the variables that + * match the regex */ + do { + if (regexec(®ex, var->value, 0, NULL, 0) == 0) { + /* early termination by the user is not an error; + * just break and return successfully */ + if (fn(var->value, data) < 0) + break; + } + + var = var->next; + } while (var != NULL); + regfree(®ex); + } else { + /* no regex; go through all the variables */ + do { + /* early termination by the user is not an error; + * just break and return successfully */ + if (fn(var->value, data) < 0) + break; + + var = var->next; + } while (var != NULL); + } - exit: - if (regexp != NULL) - regfree(&preg); - return error; + return 0; } -static int config_set_multivar(git_config_file *cfg, const char *name, const char *regexp, const char *value) +static int config_set_multivar( + git_config_file *cfg, const char *name, const char *regexp, const char *value) { - int error, replaced = 0; + int replaced = 0; cvar_t *var, *newvar; diskfile_backend *b = (diskfile_backend *)cfg; char *key; regex_t preg; + int result; + khiter_t pos; - if (regexp == NULL) - return git__throw(GIT_EINVALIDARGS, "No regex supplied"); + assert(regexp); - if ((error = normalize_name(name, &key)) < GIT_SUCCESS) - return error; + if (normalize_name(name, &key) < 0) + return -1; - var = git_hashtable_lookup(b->values, key); - if (var == NULL) { - free(key); - return git__throw(GIT_ENOTFOUND, "Variable '%s' not found", name); + pos = git_strmap_lookup_index(b->values, key); + if (!git_strmap_valid_index(b->values, pos)) { + git__free(key); + return GIT_ENOTFOUND; } - error = regcomp(&preg, regexp, REG_EXTENDED); - if (error < 0) { - free(key); - return git__throw(GIT_EINVALIDARGS, "Failed to compile regex"); + var = git_strmap_value_at(b->values, pos); + + result = regcomp(&preg, regexp, REG_EXTENDED); + if (result < 0) { + git__free(key); + giterr_set_regex(&preg, result); + return -1; } - do { - if (!regexec(&preg, var->value, 0, NULL, 0)) { + for (;;) { + if (regexec(&preg, var->value, 0, NULL, 0) == 0) { char *tmp = git__strdup(value); - if (tmp == NULL) { - error = GIT_ENOMEM; - goto exit; - } + GITERR_CHECK_ALLOC(tmp); - free(var->value); + git__free(var->value); var->value = tmp; replaced = 1; } - if (var->next != NULL) - var = var->next; - else + if (var->next == NULL) break; - } while (var != NULL); + + var = var->next; + } /* If we've reached the end of the variables and we haven't found it yet, we need to append it */ if (!replaced) { newvar = git__malloc(sizeof(cvar_t)); - if (newvar == NULL) { - error = GIT_ENOMEM; - goto exit; - } + GITERR_CHECK_ALLOC(newvar); memset(newvar, 0x0, sizeof(cvar_t)); + newvar->key = git__strdup(var->key); - if (newvar->key == NULL) { - error = GIT_ENOMEM; - goto exit; - } + GITERR_CHECK_ALLOC(newvar->key); + newvar->value = git__strdup(value); - if (newvar->value == NULL) { - error = GIT_ENOMEM; - goto exit; - } + GITERR_CHECK_ALLOC(newvar->value); var->next = newvar; } - error = config_write(b, key, &preg, value); - if (error < GIT_SUCCESS) { - error = git__rethrow(error, "Failed to update value in file"); - goto exit; - } + result = config_write(b, key, &preg, value); - exit: - free(key); + git__free(key); regfree(&preg); - return error; + + return result; } static int config_delete(git_config_file *cfg, const char *name) { - int error; - const cvar_t *var; - cvar_t *old_value; + cvar_t *var; diskfile_backend *b = (diskfile_backend *)cfg; char *key; + int result; + khiter_t pos; - if ((error = normalize_name(name, &key)) < GIT_SUCCESS) - return error; + if (normalize_name(name, &key) < 0) + return -1; - var = git_hashtable_lookup(b->values, key); - free(key); + pos = git_strmap_lookup_index(b->values, key); + git__free(key); - if (var == NULL) - return git__throw(GIT_ENOTFOUND, "Variable '%s' not found", name); + if (!git_strmap_valid_index(b->values, pos)) + return GIT_ENOTFOUND; - if (var->next != NULL) - return git__throw(GIT_EINVALIDARGS, "Multivar incompatible with simple delete"); + var = git_strmap_value_at(b->values, pos); + if (var->next != NULL) { + giterr_set(GITERR_CONFIG, "Cannot delete multivar with a single delete"); + return -1; + } - if ((error = git_hashtable_remove2(b->values, var->key, (void **)&old_value)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to remove %s from hashtable", key); + git_strmap_delete_at(b->values, pos); - error = config_write(b, var->key, NULL, NULL); - cvar_free(old_value); + result = config_write(b, var->key, NULL, NULL); - return error; + cvar_free(var); + return result; } int git_config_file__ondisk(git_config_file **out, const char *path) @@ -450,16 +466,12 @@ int git_config_file__ondisk(git_config_file **out, const char *path) diskfile_backend *backend; backend = git__malloc(sizeof(diskfile_backend)); - if (backend == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(backend); memset(backend, 0x0, sizeof(diskfile_backend)); backend->file_path = git__strdup(path); - if (backend->file_path == NULL) { - git__free(backend); - return GIT_ENOMEM; - } + GITERR_CHECK_ALLOC(backend->file_path); backend->parent.open = config_open; backend->parent.get = config_get; @@ -472,7 +484,7 @@ int git_config_file__ondisk(git_config_file **out, const char *path) *out = (git_config_file *)backend; - return GIT_SUCCESS; + return 0; } static int cfg_getchar_raw(diskfile_backend *cfg) @@ -551,7 +563,7 @@ static int cfg_peek(diskfile_backend *cfg, int flags) /* * Read and consume a line, returning it in newly-allocated memory. */ -static char *cfg_readline(diskfile_backend *cfg) +static char *cfg_readline(diskfile_backend *cfg, bool skip_whitespace) { char *line = NULL; char *line_src, *line_end; @@ -559,9 +571,11 @@ static char *cfg_readline(diskfile_backend *cfg) line_src = cfg->reader.read_ptr; - /* Skip empty empty lines */ - while (isspace(*line_src)) - ++line_src; + if (skip_whitespace) { + /* Skip empty empty lines */ + while (isspace(*line_src)) + ++line_src; + } line_end = strchr(line_src, '\n'); @@ -621,12 +635,11 @@ GIT_INLINE(int) config_keychar(int c) return isalnum(c) || c == '-'; } -static int parse_section_header_ext(const char *line, const char *base_name, char **section_name) +static int parse_section_header_ext(diskfile_backend *cfg, const char *line, const char *base_name, char **section_name) { int c, rpos; char *first_quote, *last_quote; git_buf buf = GIT_BUF_INIT; - int error = GIT_SUCCESS; int quote_marks; /* * base_name is what came before the space. We should be at the @@ -637,8 +650,10 @@ static int parse_section_header_ext(const char *line, const char *base_name, cha first_quote = strchr(line, '"'); last_quote = strrchr(line, '"'); - if (last_quote - first_quote == 0) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse ext header. There is no final quotation mark"); + if (last_quote - first_quote == 0) { + set_parse_error(cfg, 0, "Missing closing quotation mark in section header"); + return -1; + } git_buf_grow(&buf, strlen(base_name) + last_quote - first_quote + 2); git_buf_printf(&buf, "%s.", base_name); @@ -655,27 +670,30 @@ static int parse_section_header_ext(const char *line, const char *base_name, cha */ do { if (quote_marks == 2) { - puts("too many marks"); - error = git__throw(GIT_EOBJCORRUPTED, "Falied to parse ext header. Text after closing quote"); - goto out; - + set_parse_error(cfg, rpos, "Unexpected text after closing quotes"); + git_buf_free(&buf); + return -1; } switch (c) { case '"': ++quote_marks; continue; + case '\\': c = line[rpos++]; + switch (c) { case '"': case '\\': break; + default: - error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse ext header. Unsupported escape char \\%c", c); - goto out; + set_parse_error(cfg, rpos, "Unsupported escape sequence"); + git_buf_free(&buf); + return -1; } - break; + default: break; } @@ -683,61 +701,53 @@ static int parse_section_header_ext(const char *line, const char *base_name, cha git_buf_putc(&buf, c); } while ((c = line[rpos++]) != ']'); - *section_name = git__strdup(git_buf_cstr(&buf)); - out: - git_buf_free(&buf); - - return error; + *section_name = git_buf_detach(&buf); + return 0; } static int parse_section_header(diskfile_backend *cfg, char **section_out) { char *name, *name_end; int name_length, c, pos; - int error = GIT_SUCCESS; + int result; char *line; - line = cfg_readline(cfg); + line = cfg_readline(cfg, true); if (line == NULL) - return GIT_ENOMEM; + return -1; /* find the end of the variable's name */ name_end = strchr(line, ']'); if (name_end == NULL) { git__free(line); - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse header. Can't find header name end"); + set_parse_error(cfg, 0, "Missing ']' in section header"); + return -1; } name = (char *)git__malloc((size_t)(name_end - line) + 1); - if (name == NULL) { - git__free(line); - return GIT_ENOMEM; - } + GITERR_CHECK_ALLOC(name); name_length = 0; pos = 0; /* Make sure we were given a section header */ c = line[pos++]; - if (c != '[') { - error = git__throw(GIT_ERROR, "Failed to parse header. Didn't get section header. This is a bug"); - goto error; - } + assert(c == '['); c = line[pos++]; do { if (isspace(c)){ name[name_length] = '\0'; - error = parse_section_header_ext(line, name, section_out); + result = parse_section_header_ext(cfg, line, name, section_out); git__free(line); git__free(name); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse header"); + return result; } if (!config_keychar(c) && c != '.') { - error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse header. Wrong format on header"); - goto error; + set_parse_error(cfg, pos, "Unexpected character in header"); + goto fail_parse; } name[name_length++] = (char) tolower(c); @@ -745,20 +755,21 @@ static int parse_section_header(diskfile_backend *cfg, char **section_out) } while ((c = line[pos++]) != ']'); if (line[pos - 1] != ']') { - error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse header. Config file ended unexpectedly"); - goto error; + set_parse_error(cfg, pos, "Unexpected end of file"); + goto fail_parse; } - name[name_length] = 0; git__free(line); - git__strtolower(name); + + name[name_length] = 0; *section_out = name; - return GIT_SUCCESS; -error: + return 0; + +fail_parse: git__free(line); git__free(name); - return error; + return -1; } static int skip_bom(diskfile_backend *cfg) @@ -766,7 +777,7 @@ static int skip_bom(diskfile_backend *cfg) static const char utf8_bom[] = "\xef\xbb\xbf"; if (cfg->reader.buffer.size < sizeof(utf8_bom)) - return GIT_SUCCESS; + return 0; if (memcmp(cfg->reader.read_ptr, utf8_bom, sizeof(utf8_bom)) == 0) cfg->reader.read_ptr += sizeof(utf8_bom); @@ -775,7 +786,7 @@ static int skip_bom(diskfile_backend *cfg) shit with the BoM */ - return GIT_SUCCESS; + return 0; } /* @@ -817,9 +828,9 @@ static int skip_bom(diskfile_backend *cfg) boolean_false = "no" | "0" | "false" | "off" */ -static void strip_comments(char *line) +static int strip_comments(char *line, int in_quotes) { - int quote_count = 0; + int quote_count = in_quotes; char *ptr; for (ptr = line; *ptr; ++ptr) { @@ -832,19 +843,25 @@ static void strip_comments(char *line) } } + /* skip any space at the end */ if (isspace(ptr[-1])) { - /* TODO skip whitespace */ + ptr--; } + ptr[0] = '\0'; + + return quote_count; } static int config_parse(diskfile_backend *cfg_file) { - int error = GIT_SUCCESS, c; + int c; char *current_section = NULL; char *var_name; char *var_value; cvar_t *var, *existing; git_buf buf = GIT_BUF_INIT; + int result = 0; + khiter_t pos; /* Initialize the reading position */ cfg_file->reader.read_ptr = cfg_file->reader.buffer.ptr; @@ -852,11 +869,11 @@ static int config_parse(diskfile_backend *cfg_file) /* If the file is empty, there's nothing for us to do */ if (*cfg_file->reader.read_ptr == '\0') - return GIT_SUCCESS; + return 0; skip_bom(cfg_file); - while (error == GIT_SUCCESS && !cfg_file->reader.eof) { + while (result == 0 && !cfg_file->reader.eof) { c = cfg_peek(cfg_file, SKIP_WHITESPACE); @@ -868,7 +885,7 @@ static int config_parse(diskfile_backend *cfg_file) case '[': /* section header, new section begins */ git__free(current_section); current_section = NULL; - error = parse_section_header(cfg_file, ¤t_section); + result = parse_section_header(cfg_file, ¤t_section); break; case ';': @@ -877,16 +894,12 @@ static int config_parse(diskfile_backend *cfg_file) break; default: /* assume variable declaration */ - error = parse_variable(cfg_file, &var_name, &var_value); - - if (error < GIT_SUCCESS) + result = parse_variable(cfg_file, &var_name, &var_value); + if (result < 0) break; var = git__malloc(sizeof(cvar_t)); - if (var == NULL) { - error = GIT_ENOMEM; - break; - } + GITERR_CHECK_ALLOC(var); memset(var, 0x0, sizeof(cvar_t)); @@ -894,19 +907,21 @@ static int config_parse(diskfile_backend *cfg_file) git_buf_printf(&buf, "%s.%s", current_section, var_name); git__free(var_name); - if (git_buf_oom(&buf)) { - error = GIT_ENOMEM; - break; - } + if (git_buf_oom(&buf)) + return -1; var->key = git_buf_detach(&buf); var->value = var_value; /* Add or append the new config option */ - existing = git_hashtable_lookup(cfg_file->values, var->key); - if (existing == NULL) { - error = git_hashtable_insert(cfg_file->values, var->key, var); + pos = git_strmap_lookup_index(cfg_file->values, var->key); + if (!git_strmap_valid_index(cfg_file->values, pos)) { + git_strmap_insert(cfg_file->values, var->key, var, result); + if (result < 0) + break; + result = 0; } else { + existing = git_strmap_value_at(cfg_file->values, pos); while (existing->next != NULL) { existing = existing->next; } @@ -918,13 +933,12 @@ static int config_parse(diskfile_backend *cfg_file) } git__free(current_section); - - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse config"); + return result; } static int write_section(git_filebuf *file, const char *key) { - int error; + int result; const char *dot; git_buf buf = GIT_BUF_INIT; @@ -939,13 +953,14 @@ static int write_section(git_filebuf *file, const char *key) git_buf_printf(&buf, " \"%s\"", dot + 1); } git_buf_puts(&buf, "]\n"); + if (git_buf_oom(&buf)) - return GIT_ENOMEM; + return -1; - error = git_filebuf_write(file, git_buf_cstr(&buf), buf.size); + result = git_filebuf_write(file, git_buf_cstr(&buf), buf.size); git_buf_free(&buf); - return error; + return result; } /* @@ -953,50 +968,45 @@ static int write_section(git_filebuf *file, const char *key) */ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char* value) { - int error = GIT_SUCCESS, c; - int section_matches = 0, last_section_matched = 0, preg_replaced = 0; + int result, c; + int section_matches = 0, last_section_matched = 0, preg_replaced = 0, write_trailer = 0; + const char *pre_end = NULL, *post_start = NULL, *data_start; char *current_section = NULL, *section, *name, *ldot; - char *var_name, *var_value; git_filebuf file = GIT_FILEBUF_INIT; - const char *pre_end = NULL, *post_start = NULL, *data_start; /* We need to read in our own config file */ - error = git_futils_readbuffer(&cfg->reader.buffer, cfg->file_path); - if (error < GIT_SUCCESS && error != GIT_ENOTFOUND) { - return git__rethrow(error, "Failed to read existing config file %s", cfg->file_path); - } + result = git_futils_readbuffer(&cfg->reader.buffer, cfg->file_path); /* Initialise the reading position */ - if (error == GIT_ENOTFOUND) { - error = GIT_SUCCESS; + if (result == GIT_ENOTFOUND) { cfg->reader.read_ptr = NULL; cfg->reader.eof = 1; data_start = NULL; git_buf_clear(&cfg->reader.buffer); - } else { + } else if (result == 0) { cfg->reader.read_ptr = cfg->reader.buffer.ptr; cfg->reader.eof = 0; data_start = cfg->reader.read_ptr; + } else { + return -1; /* OS error when reading the file */ } /* Lock the file */ - error = git_filebuf_open(&file, cfg->file_path, 0); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to lock config file"); + if (git_filebuf_open(&file, cfg->file_path, 0) < 0) + return -1; skip_bom(cfg); ldot = strrchr(key, '.'); name = ldot + 1; section = git__strndup(key, ldot - key); - while (error == GIT_SUCCESS && !cfg->reader.eof) { + while (!cfg->reader.eof) { c = cfg_peek(cfg, SKIP_WHITESPACE); - switch (c) { - case '\0': /* We've arrived at the end of the file */ + if (c == '\0') { /* We've arrived at the end of the file */ break; - case '[': /* section header, new section begins */ + } else if (c == '[') { /* section header, new section begins */ /* * We set both positions to the current one in case we * need to add a variable to the end of a section. In that @@ -1005,23 +1015,21 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p * default case will take care of updating them. */ pre_end = post_start = cfg->reader.read_ptr; - if (current_section) - git__free(current_section); - error = parse_section_header(cfg, ¤t_section); - if (error < GIT_SUCCESS) - break; + + git__free(current_section); + if (parse_section_header(cfg, ¤t_section) < 0) + goto rewrite_fail; /* Keep track of when it stops matching */ last_section_matched = section_matches; section_matches = !strcmp(current_section, section); - break; + } - case ';': - case '#': + else if (c == ';' || c == '#') { cfg_consume_line(cfg); - break; + } - default: + else { /* * If the section doesn't match, but the last section did, * it means we need to add a variable (so skip the line @@ -1035,67 +1043,54 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p if (!section_matches) { if (!last_section_matched) { cfg_consume_line(cfg); - break; + continue; } } else { - int cmp = -1; + int has_matched = 0; + char *var_name, *var_value; pre_end = cfg->reader.read_ptr; - if ((error = parse_variable(cfg, &var_name, &var_value)) == GIT_SUCCESS) - cmp = strcasecmp(name, var_name); + if (parse_variable(cfg, &var_name, &var_value) < 0) + goto rewrite_fail; + + /* First try to match the name of the variable */ + if (strcasecmp(name, var_name) == 0) + has_matched = 1; - if (cmp == 0 && preg != NULL) - cmp = regexec(preg, var_value, 0, NULL, 0); + /* If the name matches, and we have a regex to match the + * value, try to match it */ + if (has_matched && preg != NULL) + has_matched = (regexec(preg, var_value, 0, NULL, 0) == 0); git__free(var_name); git__free(var_value); - if (cmp != 0) - break; + /* if there is no match, keep going */ + if (!has_matched) + continue; post_start = cfg->reader.read_ptr; } - /* - * We've found the variable we wanted to change, so - * write anything up to it - */ + /* We've found the variable we wanted to change, so + * write anything up to it */ + git_filebuf_write(&file, data_start, pre_end - data_start); preg_replaced = 1; - error = git_filebuf_write(&file, data_start, pre_end - data_start); - if (error < GIT_SUCCESS) { - git__rethrow(error, "Failed to write the first part of the file"); - break; - } - /* - * Then replace the variable. If the value is NULL, it - * means we want to delete it, so pretend everything went - * fine - */ - if (value == NULL) - error = GIT_SUCCESS; - else - error = git_filebuf_printf(&file, "\t%s = %s\n", name, value); - if (error < GIT_SUCCESS) { - git__rethrow(error, "Failed to overwrite the variable"); - break; + /* Then replace the variable. If the value is NULL, it + * means we want to delete it, so don't write anything. */ + if (value != NULL) { + git_filebuf_printf(&file, "\t%s = %s\n", name, value); } + /* multiline variable? we need to keep reading lines to match */ if (preg != NULL) { data_start = post_start; continue; } - /* And then the write out rest of the file */ - error = git_filebuf_write(&file, post_start, - cfg->reader.buffer.size - (post_start - data_start)); - - if (error < GIT_SUCCESS) { - git__rethrow(error, "Failed to write the rest of the file"); - break; - } - - goto cleanup; + write_trailer = 1; + break; /* break from the loop */ } } @@ -1115,134 +1110,159 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p * want to write the rest of the file. Otherwise we need to write * out the whole file and then the new variable. */ - if (preg_replaced) { - error = git_filebuf_printf(&file, "\n%s", data_start); - if (error < GIT_SUCCESS) - error = git__rethrow(error, "Failed to write the rest of the file"); - - goto cleanup; - } + if (write_trailer) { + /* Write out rest of the file */ + git_filebuf_write(&file, post_start, cfg->reader.buffer.size - (post_start - data_start)); + } else { + if (preg_replaced) { + git_filebuf_printf(&file, "\n%s", data_start); + } else { + git_filebuf_write(&file, cfg->reader.buffer.ptr, cfg->reader.buffer.size); + + /* And now if we just need to add a variable */ + if (!section_matches && write_section(&file, section) < 0) + goto rewrite_fail; + + /* Sanity check: if we are here, and value is NULL, that means that somebody + * touched the config file after our intial read. We should probably assert() + * this, but instead we'll handle it gracefully with an error. */ + if (value == NULL) { + giterr_set(GITERR_CONFIG, + "Race condition when writing a config file (a cvar has been removed)"); + goto rewrite_fail; + } - error = git_filebuf_write(&file, cfg->reader.buffer.ptr, cfg->reader.buffer.size); - if (error < GIT_SUCCESS) { - git__rethrow(error, "Failed to write original config content"); - goto cleanup; + git_filebuf_printf(&file, "\t%s = %s\n", name, value); + } } - /* And now if we just need to add a variable */ - if (section_matches) { - error = git_filebuf_printf(&file, "\t%s = %s\n", name, value); - goto cleanup; - } + git__free(section); + git__free(current_section); - /* Or maybe we need to write out a whole section */ - error = write_section(&file, section); - if (error < GIT_SUCCESS) - git__rethrow(error, "Failed to write new section"); + result = git_filebuf_commit(&file, GIT_CONFIG_FILE_MODE); + git_buf_free(&cfg->reader.buffer); + return result; - error = git_filebuf_printf(&file, "\t%s = %s\n", name, value); - cleanup: +rewrite_fail: git__free(section); git__free(current_section); - if (error < GIT_SUCCESS) - git_filebuf_cleanup(&file); - else - error = git_filebuf_commit(&file, GIT_CONFIG_FILE_MODE); - + git_filebuf_cleanup(&file); git_buf_free(&cfg->reader.buffer); - return error; + return -1; } -static int is_multiline_var(const char *str) +/* '\"' -> '"' etc */ +static char *fixup_line(const char *ptr, int quote_count) { - char *end = strrchr(str, '\0') - 1; + char *str = git__malloc(strlen(ptr) + 1); + char *out = str, *esc; + const char *escapes = "ntb\"\\"; + const char *escaped = "\n\t\b\"\\"; + + if (str == NULL) + return NULL; - while (isspace(*end)) - --end; + while (*ptr != '\0') { + if (*ptr == '"') { + quote_count++; + } else if (*ptr != '\\') { + *out++ = *ptr; + } else { + /* backslash, check the next char */ + ptr++; + /* if we're at the end, it's a multiline, so keep the backslash */ + if (*ptr == '\0') { + *out++ = '\\'; + goto out; + } + if ((esc = strchr(escapes, *ptr)) != NULL) { + *out++ = escaped[esc - escapes]; + } else { + git__free(str); + giterr_set(GITERR_CONFIG, "Invalid escape at %s", ptr); + return NULL; + } + } + ptr++; + } + +out: + *out = '\0'; + + return str; +} - return *end == '\\'; +static int is_multiline_var(const char *str) +{ + const char *end = str + strlen(str); + return (end > str) && (end[-1] == '\\'); } -static int parse_multiline_variable(diskfile_backend *cfg, const char *first, char **out) +static int parse_multiline_variable(diskfile_backend *cfg, git_buf *value, int in_quotes) { - char *line = NULL, *end; - int error = GIT_SUCCESS, ret; - size_t len; - char *buf; + char *line = NULL, *proc_line = NULL; + int quote_count; /* Check that the next line exists */ - line = cfg_readline(cfg); + line = cfg_readline(cfg, false); if (line == NULL) - return GIT_ENOMEM; + return -1; /* We've reached the end of the file, there is input missing */ if (line[0] == '\0') { - error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse multiline var. File ended unexpectedly"); - goto out; + set_parse_error(cfg, 0, "Unexpected end of file while parsing multine var"); + git__free(line); + return -1; } - strip_comments(line); + quote_count = strip_comments(line, !!in_quotes); /* If it was just a comment, pretend it didn't exist */ if (line[0] == '\0') { - error = parse_multiline_variable(cfg, first, out); - goto out; + git__free(line); + return parse_multiline_variable(cfg, value, quote_count); + /* TODO: unbounded recursion. This **could** be exploitable */ } - /* Find the continuation character '\' and strip the whitespace */ - end = strrchr(first, '\\'); - while (isspace(end[-1])) - --end; - - *end = '\0'; /* Terminate the string here */ + /* Drop the continuation character '\': to closely follow the UNIX + * standard, this character **has** to be last one in the buf, with + * no whitespace after it */ + assert(is_multiline_var(value->ptr)); + git_buf_truncate(value, git_buf_len(value) - 1); - len = strlen(first) + strlen(line) + 2; - buf = git__malloc(len); - if (buf == NULL) { - error = GIT_ENOMEM; - goto out; - } - - ret = p_snprintf(buf, len, "%s %s", first, line); - if (ret < 0) { - error = git__throw(GIT_EOSERR, "Failed to parse multiline var. Failed to put together two lines. OS err: %s", strerror(errno)); - git__free(buf); - goto out; + proc_line = fixup_line(line, in_quotes); + if (proc_line == NULL) { + git__free(line); + return -1; } + /* add this line to the multiline var */ + git_buf_puts(value, proc_line); + git__free(line); + git__free(proc_line); /* - * If we need to continue reading the next line, pretend - * everything we've read up to now was in one line and call - * ourselves. + * If we need to continue reading the next line, let's just + * keep putting stuff in the buffer */ - if (is_multiline_var(buf)) { - char *final_val; - error = parse_multiline_variable(cfg, buf, &final_val); - git__free(buf); - buf = final_val; - } + if (is_multiline_var(value->ptr)) + return parse_multiline_variable(cfg, value, quote_count); - *out = buf; - - out: - git__free(line); - return error; + return 0; } static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_value) { - char *tmp; - int error = GIT_SUCCESS; const char *var_end = NULL; const char *value_start = NULL; char *line; + int quote_count; - line = cfg_readline(cfg); + line = cfg_readline(cfg, true); if (line == NULL) - return GIT_ENOMEM; + return -1; - strip_comments(line); + quote_count = strip_comments(line, 0); var_end = strchr(line, '='); @@ -1256,52 +1276,42 @@ static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_val while (isspace(var_end[0])); } - tmp = git__strndup(line, var_end - line + 1); - if (tmp == NULL) { - error = GIT_ENOMEM; - goto out; - } + *var_name = git__strndup(line, var_end - line + 1); + GITERR_CHECK_ALLOC(*var_name); - *var_name = tmp; + /* If there is no value, boolean true is assumed */ + *var_value = NULL; /* * Now, let's try to parse the value */ if (value_start != NULL) { - while (isspace(value_start[0])) value_start++; - if (value_start[0] == '\0') { - *var_value = NULL; - goto out; - } - if (is_multiline_var(value_start)) { - error = parse_multiline_variable(cfg, value_start, var_value); - if (error != GIT_SUCCESS) - { - *var_value = NULL; + git_buf multi_value = GIT_BUF_INIT; + char *proc_line = fixup_line(value_start, 0); + GITERR_CHECK_ALLOC(proc_line); + git_buf_puts(&multi_value, proc_line); + git__free(proc_line); + if (parse_multiline_variable(cfg, &multi_value, quote_count) < 0 || git_buf_oom(&multi_value)) { git__free(*var_name); + git__free(line); + git_buf_free(&multi_value); + return -1; } - goto out; - } - tmp = git__strdup(value_start); - if (tmp == NULL) { - git__free(*var_name); - *var_value = NULL; - error = GIT_ENOMEM; - goto out; + *var_value = git_buf_detach(&multi_value); + + } + else if (value_start[0] != '\0') { + *var_value = fixup_line(value_start, 0); + GITERR_CHECK_ALLOC(*var_value); } - *var_value = tmp; - } else { - /* If there is no value, boolean true is assumed */ - *var_value = NULL; } - out: git__free(line); - return error; + return 0; } diff --git a/src/config_file.h b/src/config_file.h new file mode 100644 index 000000000..0080b5713 --- /dev/null +++ b/src/config_file.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_config_file_h__ +#define INCLUDE_config_file_h__ + +#include "git2/config.h" + +GIT_INLINE(int) git_config_file_open(git_config_file *cfg) +{ + return cfg->open(cfg); +} + +GIT_INLINE(void) git_config_file_free(git_config_file *cfg) +{ + cfg->free(cfg); +} + +GIT_INLINE(int) git_config_file_foreach( + git_config_file *cfg, + int (*fn)(const char *key, const char *value, void *data), + void *data) +{ + return cfg->foreach(cfg, fn, data); +} + +#endif + diff --git a/src/crlf.c b/src/crlf.c index f0ec7b736..8fe588a35 100644 --- a/src/crlf.c +++ b/src/crlf.c @@ -105,7 +105,7 @@ static int crlf_load_attributes(struct crlf_attrs *ca, git_repository *repo, con static int drop_crlf(git_buf *dest, const git_buf *source) { const char *scan = source->ptr, *next; - const char *scan_end = source->ptr + source->size; + const char *scan_end = git_buf_cstr(source) + git_buf_len(source); /* Main scan loop. Find the next carriage return and copy the * whole chunk up to that point to the destination buffer. @@ -128,8 +128,7 @@ static int drop_crlf(git_buf *dest, const git_buf *source) /* Copy remaining input into dest */ git_buf_put(dest, scan, scan_end - scan); - - return git_buf_lasterror(dest); + return 0; } static int crlf_apply_to_odb(git_filter *self, git_buf *dest, const git_buf *source) @@ -139,7 +138,7 @@ static int crlf_apply_to_odb(git_filter *self, git_buf *dest, const git_buf *sou assert(self && dest && source); /* Empty file? Nothing to do */ - if (source->size == 0) + if (git_buf_len(source) == 0) return 0; /* Heuristics to see if we can skip the conversion. diff --git a/src/delta-apply.c b/src/delta-apply.c index 24eba2bda..c8c662fa8 100644 --- a/src/delta-apply.c +++ b/src/delta-apply.c @@ -51,11 +51,15 @@ int git__delta_apply( * if not we would underflow while accessing data from the * base object, resulting in data corruption or segfault. */ - if ((hdr_sz(&base_sz, &delta, delta_end) < 0) || (base_sz != base_len)) - return git__throw(GIT_ERROR, "Failed to apply delta. Base size does not match given data"); + if ((hdr_sz(&base_sz, &delta, delta_end) < 0) || (base_sz != base_len)) { + giterr_set(GITERR_INVALID, "Failed to apply delta. Base size does not match given data"); + return -1; + } - if (hdr_sz(&res_sz, &delta, delta_end) < 0) - return git__throw(GIT_ERROR, "Failed to apply delta. Base size does not match given data"); + if (hdr_sz(&res_sz, &delta, delta_end) < 0) { + giterr_set(GITERR_INVALID, "Failed to apply delta. Base size does not match given data"); + return -1; + } if ((res_dp = git__malloc(res_sz + 1)) == NULL) return GIT_ENOMEM; @@ -111,5 +115,6 @@ int git__delta_apply( fail: git__free(out->data); out->data = NULL; - return git__throw(GIT_ERROR, "Failed to apply delta"); + giterr_set(GITERR_INVALID, "Failed to apply delta"); + return -1; } diff --git a/src/diff.c b/src/diff.c index db339d74e..b21dfaf90 100644 --- a/src/diff.c +++ b/src/diff.c @@ -8,23 +8,50 @@ #include "git2/diff.h" #include "diff.h" #include "fileops.h" +#include "config.h" +#include "attr_file.h" -static void diff_delta__free(git_diff_delta *delta) +static bool diff_pathspec_is_interesting(const git_strarray *pathspec) { - if (!delta) - return; + const char *str; - if (delta->new_file.flags & GIT_DIFF_FILE_FREE_PATH) { - git__free((char *)delta->new_file.path); - delta->new_file.path = NULL; - } + if (pathspec == NULL || pathspec->count == 0) + return false; + if (pathspec->count > 1) + return true; + + str = pathspec->strings[0]; + if (!str || !str[0] || (!str[1] && (str[0] == '*' || str[0] == '.'))) + return false; + return true; +} + +static bool diff_path_matches_pathspec(git_diff_list *diff, const char *path) +{ + unsigned int i; + git_attr_fnmatch *match; + + if (!diff->pathspec.length) + return true; + + git_vector_foreach(&diff->pathspec, i, match) { + int result = git__fnmatch(match->pattern, path, 0); + + /* if we didn't match, look for exact dirname prefix match */ + if (result == GIT_ENOMATCH && + (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 && + strncmp(path, match->pattern, match->length) == 0 && + path[match->length] == '/') + result = 0; - if (delta->old_file.flags & GIT_DIFF_FILE_FREE_PATH) { - git__free((char *)delta->old_file.path); - delta->old_file.path = NULL; + if (result == 0) + return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? false : true; + + if (result != GIT_ENOMATCH) + giterr_clear(); } - git__free(delta); + return false; } static git_diff_delta *diff_delta__alloc( @@ -36,12 +63,12 @@ static git_diff_delta *diff_delta__alloc( if (!delta) return NULL; - delta->old_file.path = git__strdup(path); + delta->old_file.path = git_pool_strdup(&diff->pool, path); if (delta->old_file.path == NULL) { git__free(delta); return NULL; } - delta->old_file.flags |= GIT_DIFF_FILE_FREE_PATH; + delta->new_file.path = delta->old_file.path; if (diff->opts.flags & GIT_DIFF_REVERSE) { @@ -56,7 +83,8 @@ static git_diff_delta *diff_delta__alloc( return delta; } -static git_diff_delta *diff_delta__dup(const git_diff_delta *d) +static git_diff_delta *diff_delta__dup( + const git_diff_delta *d, git_pool *pool) { git_diff_delta *delta = git__malloc(sizeof(git_diff_delta)); if (!delta) @@ -64,33 +92,29 @@ static git_diff_delta *diff_delta__dup(const git_diff_delta *d) memcpy(delta, d, sizeof(git_diff_delta)); - delta->old_file.path = git__strdup(d->old_file.path); - if (delta->old_file.path == NULL) { - git__free(delta); - return NULL; - } - delta->old_file.flags |= GIT_DIFF_FILE_FREE_PATH; + delta->old_file.path = git_pool_strdup(pool, d->old_file.path); + if (delta->old_file.path == NULL) + goto fail; if (d->new_file.path != d->old_file.path) { - delta->new_file.path = git__strdup(d->new_file.path); - if (delta->new_file.path == NULL) { - git__free(delta->old_file.path); - git__free(delta); - return NULL; - } - delta->new_file.flags |= GIT_DIFF_FILE_FREE_PATH; + delta->new_file.path = git_pool_strdup(pool, d->new_file.path); + if (delta->new_file.path == NULL) + goto fail; } else { delta->new_file.path = delta->old_file.path; - delta->new_file.flags &= ~GIT_DIFF_FILE_FREE_PATH; } return delta; + +fail: + git__free(delta); + return NULL; } static git_diff_delta *diff_delta__merge_like_cgit( - const git_diff_delta *a, const git_diff_delta *b) + const git_diff_delta *a, const git_diff_delta *b, git_pool *pool) { - git_diff_delta *dup = diff_delta__dup(a); + git_diff_delta *dup = diff_delta__dup(a, pool); if (!dup) return NULL; @@ -101,9 +125,7 @@ static git_diff_delta *diff_delta__merge_like_cgit( dup->new_file.mode = b->new_file.mode; dup->new_file.size = b->new_file.size; - dup->new_file.flags = - (dup->new_file.flags & GIT_DIFF_FILE_FREE_PATH) | - (b->new_file.flags & ~GIT_DIFF_FILE_FREE_PATH); + dup->new_file.flags = b->new_file.flags; /* Emulate C git for merging two diffs (a la 'git diff <sha>'). * @@ -132,10 +154,21 @@ static int diff_delta__from_one( git_delta_t status, const git_index_entry *entry) { - int error; - git_diff_delta *delta = diff_delta__alloc(diff, status, entry->path); - if (!delta) - return git__rethrow(GIT_ENOMEM, "Could not allocate diff record"); + git_diff_delta *delta; + + if (status == GIT_DELTA_IGNORED && + (diff->opts.flags & GIT_DIFF_INCLUDE_IGNORED) == 0) + return 0; + + if (status == GIT_DELTA_UNTRACKED && + (diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) + return 0; + + if (!diff_path_matches_pathspec(diff, entry->path)) + return 0; + + delta = diff_delta__alloc(diff, status, entry->path); + GITERR_CHECK_ALLOC(delta); /* This fn is just for single-sided diffs */ assert(status != GIT_DELTA_MODIFIED); @@ -153,10 +186,12 @@ static int diff_delta__from_one( delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID; delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID; - if ((error = git_vector_insert(&diff->deltas, delta)) < GIT_SUCCESS) - diff_delta__free(delta); + if (git_vector_insert(&diff->deltas, delta) < 0) { + git__free(delta); + return -1; + } - return error; + return 0; } static int diff_delta__from_two( @@ -166,9 +201,12 @@ static int diff_delta__from_two( const git_index_entry *new_entry, git_oid *new_oid) { - int error; git_diff_delta *delta; + if (status == GIT_DELTA_UNMODIFIED && + (diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) + return 0; + if ((diff->opts.flags & GIT_DIFF_REVERSE) != 0) { const git_index_entry *temp = old_entry; old_entry = new_entry; @@ -176,8 +214,7 @@ static int diff_delta__from_two( } delta = diff_delta__alloc(diff, status, old_entry->path); - if (!delta) - return git__rethrow(GIT_ENOMEM, "Could not allocate diff record"); + GITERR_CHECK_ALLOC(delta); delta->old_file.mode = old_entry->mode; git_oid_cpy(&delta->old_file.oid, &old_entry->oid); @@ -188,28 +225,23 @@ static int diff_delta__from_two( if (new_oid || !git_oid_iszero(&new_entry->oid)) delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID; - if ((error = git_vector_insert(&diff->deltas, delta)) < GIT_SUCCESS) - diff_delta__free(delta); + if (git_vector_insert(&diff->deltas, delta) < 0) { + git__free(delta); + return -1; + } - return error; + return 0; } -#define DIFF_OLD_PREFIX_DEFAULT "a/" -#define DIFF_NEW_PREFIX_DEFAULT "b/" - -static char *diff_strdup_prefix(const char *prefix) +static char *diff_strdup_prefix(git_pool *pool, const char *prefix) { size_t len = strlen(prefix); - char *str = git__malloc(len + 2); - if (str != NULL) { - memcpy(str, prefix, len + 1); - /* append '/' at end if needed */ - if (len > 0 && str[len - 1] != '/') { - str[len] = '/'; - str[len + 1] = '\0'; - } - } - return str; + + /* append '/' at end if needed */ + if (len > 0 && prefix[len - 1] != '/') + return git_pool_strcat(pool, prefix, "/"); + else + return git_pool_strndup(pool, prefix, len + 1); } static int diff_delta__cmp(const void *a, const void *b) @@ -219,29 +251,55 @@ static int diff_delta__cmp(const void *a, const void *b) return val ? val : ((int)da->status - (int)db->status); } +static int config_bool(git_config *cfg, const char *name, int defvalue) +{ + int val = defvalue; + if (git_config_get_bool(cfg, name, &val) < 0) + giterr_clear(); + return val; +} + static git_diff_list *git_diff_list_alloc( git_repository *repo, const git_diff_options *opts) { + git_config *cfg; + size_t i; git_diff_list *diff = git__calloc(1, sizeof(git_diff_list)); if (diff == NULL) return NULL; diff->repo = repo; + if (git_vector_init(&diff->deltas, 0, diff_delta__cmp) < 0 || + git_pool_init(&diff->pool, 1, 0) < 0) + goto fail; + + /* load config values that affect diff behavior */ + if (git_repository_config__weakptr(&cfg, repo) < 0) + goto fail; + if (config_bool(cfg, "core.symlinks", 1)) + diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_HAS_SYMLINKS; + if (config_bool(cfg, "core.ignorestat", 0)) + diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_ASSUME_UNCHANGED; + if (config_bool(cfg, "core.filemode", 1)) + diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_EXEC_BIT; + if (config_bool(cfg, "core.trustctime", 1)) + diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME; + /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */ + if (opts == NULL) return diff; memcpy(&diff->opts, opts, sizeof(git_diff_options)); + memset(&diff->opts.pathspec, 0, sizeof(diff->opts.pathspec)); - diff->opts.old_prefix = diff_strdup_prefix( + diff->opts.old_prefix = diff_strdup_prefix(&diff->pool, opts->old_prefix ? opts->old_prefix : DIFF_OLD_PREFIX_DEFAULT); - diff->opts.new_prefix = diff_strdup_prefix( + diff->opts.new_prefix = diff_strdup_prefix(&diff->pool, opts->new_prefix ? opts->new_prefix : DIFF_NEW_PREFIX_DEFAULT); - if (!diff->opts.old_prefix || !diff->opts.new_prefix) { - git__free(diff); - return NULL; - } + if (!diff->opts.old_prefix || !diff->opts.new_prefix) + goto fail; if (diff->opts.flags & GIT_DIFF_REVERSE) { char *swap = diff->opts.old_prefix; @@ -249,33 +307,63 @@ static git_diff_list *git_diff_list_alloc( diff->opts.new_prefix = swap; } - if (git_vector_init(&diff->deltas, 0, diff_delta__cmp) < GIT_SUCCESS) { - git__free(diff->opts.old_prefix); - git__free(diff->opts.new_prefix); - git__free(diff); - return NULL; - } + /* only copy pathspec if it is "interesting" so we can test + * diff->pathspec.length > 0 to know if it is worth calling + * fnmatch as we iterate. + */ + if (!diff_pathspec_is_interesting(&opts->pathspec)) + return diff; - /* do something safe with the pathspec strarray */ + if (git_vector_init( + &diff->pathspec, (unsigned int)opts->pathspec.count, NULL) < 0) + goto fail; + + for (i = 0; i < opts->pathspec.count; ++i) { + int ret; + const char *pattern = opts->pathspec.strings[i]; + git_attr_fnmatch *match = git__calloc(1, sizeof(git_attr_fnmatch)); + if (!match) + goto fail; + ret = git_attr_fnmatch__parse(match, &diff->pool, NULL, &pattern); + if (ret == GIT_ENOTFOUND) { + git__free(match); + continue; + } else if (ret < 0) + goto fail; + + if (git_vector_insert(&diff->pathspec, match) < 0) + goto fail; + } return diff; + +fail: + git_diff_list_free(diff); + return NULL; } void git_diff_list_free(git_diff_list *diff) { git_diff_delta *delta; + git_attr_fnmatch *match; unsigned int i; if (!diff) return; git_vector_foreach(&diff->deltas, i, delta) { - diff_delta__free(delta); + git__free(delta); diff->deltas.contents[i] = NULL; } git_vector_free(&diff->deltas); - git__free(diff->opts.old_prefix); - git__free(diff->opts.new_prefix); + + git_vector_foreach(&diff->pathspec, i, match) { + git__free(match); + diff->pathspec.contents[i] = NULL; + } + git_vector_free(&diff->pathspec); + + git_pool_clear(&diff->pool); git__free(diff); } @@ -284,27 +372,24 @@ static int oid_for_workdir_item( const git_index_entry *item, git_oid *oid) { - int error = GIT_SUCCESS; + int result; git_buf full_path = GIT_BUF_INIT; - error = git_buf_joinpath( - &full_path, git_repository_workdir(repo), item->path); - if (error != GIT_SUCCESS) - return error; + if (git_buf_joinpath(&full_path, git_repository_workdir(repo), item->path) < 0) + return -1; - /* otherwise calculate OID for file */ + /* calculate OID for file if possible*/ if (S_ISLNK(item->mode)) - error = git_odb__hashlink(oid, full_path.ptr); - else if (!git__is_sizet(item->file_size)) - error = git__throw(GIT_ERROR, "File size overflow for 32-bit systems"); - else { - int fd; - - if ((fd = p_open(full_path.ptr, O_RDONLY)) < 0) - error = git__throw( - GIT_EOSERR, "Could not open '%s'", item->path); + result = git_odb__hashlink(oid, full_path.ptr); + else if (!git__is_sizet(item->file_size)) { + giterr_set(GITERR_OS, "File size overflow for 32-bit systems"); + result = -1; + } else { + int fd = git_futils_open_ro(full_path.ptr); + if (fd < 0) + result = fd; else { - error = git_odb__hashfd( + result = git_odb__hashfd( oid, fd, (size_t)item->file_size, GIT_OBJ_BLOB); p_close(fd); } @@ -312,9 +397,11 @@ static int oid_for_workdir_item( git_buf_free(&full_path); - return error; + return result; } +#define EXEC_BIT_MASK 0000111 + static int maybe_modified( git_iterator *old_iter, const git_index_entry *oitem, @@ -322,55 +409,96 @@ static int maybe_modified( const git_index_entry *nitem, git_diff_list *diff) { - int error = GIT_SUCCESS; git_oid noid, *use_noid = NULL; + git_delta_t status = GIT_DELTA_MODIFIED; + unsigned int omode = oitem->mode; + unsigned int nmode = nitem->mode; GIT_UNUSED(old_iter); - /* support "assume unchanged" & "skip worktree" bits */ - if ((oitem->flags_extended & GIT_IDXENTRY_INTENT_TO_ADD) != 0 || - (oitem->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE) != 0) - return GIT_SUCCESS; + if (!diff_path_matches_pathspec(diff, oitem->path)) + return 0; + + /* on platforms with no symlinks, promote plain files to symlinks */ + if (S_ISLNK(omode) && S_ISREG(nmode) && + !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS)) + nmode = GIT_MODE_TYPE(omode) | (nmode & GIT_MODE_PERMS_MASK); - if (GIT_MODE_TYPE(oitem->mode) != GIT_MODE_TYPE(nitem->mode)) { - error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem); - if (error == GIT_SUCCESS) - error = diff_delta__from_one(diff, GIT_DELTA_ADDED, nitem); - return error; + /* on platforms with no execmode, clear exec bit from comparisons */ + if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_EXEC_BIT)) { + omode = omode & ~EXEC_BIT_MASK; + nmode = nmode & ~EXEC_BIT_MASK; } - if (git_oid_cmp(&oitem->oid, &nitem->oid) == 0 && - oitem->mode == nitem->mode) - return GIT_SUCCESS; + /* support "assume unchanged" (badly, b/c we still stat everything) */ + if ((diff->diffcaps & GIT_DIFFCAPS_ASSUME_UNCHANGED) != 0) + status = (oitem->flags_extended & GIT_IDXENTRY_INTENT_TO_ADD) ? + GIT_DELTA_MODIFIED : GIT_DELTA_UNMODIFIED; + + /* support "skip worktree" index bit */ + else if ((oitem->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE) != 0) + status = GIT_DELTA_UNMODIFIED; + + /* if basic type of file changed, then split into delete and add */ + else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) { + if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 || + diff_delta__from_one(diff, GIT_DELTA_ADDED, nitem) < 0) + return -1; + return 0; + } + + /* if oids and modes match, then file is unmodified */ + else if (git_oid_cmp(&oitem->oid, &nitem->oid) == 0 && + omode == nmode) + status = GIT_DELTA_UNMODIFIED; + + /* if we have a workdir item with an unknown oid, check deeper */ + else if (git_oid_iszero(&nitem->oid) && new_iter->type == GIT_ITERATOR_WORKDIR) { + /* TODO: add check against index file st_mtime to avoid racy-git */ - if (git_oid_iszero(&nitem->oid) && new_iter->type == GIT_ITERATOR_WORKDIR) { /* if they files look exactly alike, then we'll assume the same */ if (oitem->file_size == nitem->file_size && - oitem->ctime.seconds == nitem->ctime.seconds && + (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) || + (oitem->ctime.seconds == nitem->ctime.seconds)) && oitem->mtime.seconds == nitem->mtime.seconds && - oitem->dev == nitem->dev && + (!(diff->diffcaps & GIT_DIFFCAPS_USE_DEV) || + (oitem->dev == nitem->dev)) && oitem->ino == nitem->ino && oitem->uid == nitem->uid && oitem->gid == nitem->gid) - return GIT_SUCCESS; + status = GIT_DELTA_UNMODIFIED; + + else if (S_ISGITLINK(nmode)) { + git_submodule *sub; + + if ((diff->opts.flags & GIT_DIFF_IGNORE_SUBMODULES) != 0) + status = GIT_DELTA_UNMODIFIED; + else if (git_submodule_lookup(&sub, diff->repo, nitem->path) < 0) + return -1; + else if (sub->ignore == GIT_SUBMODULE_IGNORE_ALL) + status = GIT_DELTA_UNMODIFIED; + else { + /* TODO: support other GIT_SUBMODULE_IGNORE values */ + status = GIT_DELTA_UNMODIFIED; + } + } /* TODO: check git attributes so we will not have to read the file * in if it is marked binary. */ - error = oid_for_workdir_item(diff->repo, nitem, &noid); - if (error != GIT_SUCCESS) - return error; - if (git_oid_cmp(&oitem->oid, &noid) == 0 && - oitem->mode == nitem->mode) - return GIT_SUCCESS; + else if (oid_for_workdir_item(diff->repo, nitem, &noid) < 0) + return -1; + + else if (git_oid_cmp(&oitem->oid, &noid) == 0 && + omode == nmode) + status = GIT_DELTA_UNMODIFIED; /* store calculated oid so we don't have to recalc later */ use_noid = &noid; } - return diff_delta__from_two( - diff, GIT_DELTA_MODIFIED, oitem, nitem, use_noid); + return diff_delta__from_two(diff, status, oitem, nitem, use_noid); } static int diff_from_iterators( @@ -380,37 +508,33 @@ static int diff_from_iterators( git_iterator *new_iter, git_diff_list **diff_ptr) { - int error; const git_index_entry *oitem, *nitem; char *ignore_prefix = NULL; git_diff_list *diff = git_diff_list_alloc(repo, opts); - if (!diff) { - error = GIT_ENOMEM; - goto cleanup; - } + if (!diff) + goto fail; diff->old_src = old_iter->type; diff->new_src = new_iter->type; - if ((error = git_iterator_current(old_iter, &oitem)) < GIT_SUCCESS || - (error = git_iterator_current(new_iter, &nitem)) < GIT_SUCCESS) - goto cleanup; + if (git_iterator_current(old_iter, &oitem) < 0 || + git_iterator_current(new_iter, &nitem) < 0) + goto fail; /* run iterators building diffs */ - while (!error && (oitem || nitem)) { + while (oitem || nitem) { /* create DELETED records for old items not matched in new */ if (oitem && (!nitem || strcmp(oitem->path, nitem->path) < 0)) { - error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem); - if (error == GIT_SUCCESS) - error = git_iterator_advance(old_iter, &oitem); - continue; + if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 || + git_iterator_advance(old_iter, &oitem) < 0) + goto fail; } /* create ADDED, TRACKED, or IGNORED records for new items not * matched in old (and/or descend into directories as needed) */ - if (nitem && (!oitem || strcmp(oitem->path, nitem->path) > 0)) { + else if (nitem && (!oitem || strcmp(oitem->path, nitem->path) > 0)) { int is_ignored; git_delta_t delta_type = GIT_DELTA_ADDED; @@ -418,17 +542,27 @@ static int diff_from_iterators( if (ignore_prefix != NULL && git__prefixcmp(nitem->path, ignore_prefix) == 0) { - error = git_iterator_advance(new_iter, &nitem); + if (git_iterator_advance(new_iter, &nitem) < 0) + goto fail; + continue; } is_ignored = git_iterator_current_is_ignored(new_iter); if (S_ISDIR(nitem->mode)) { - if (git__prefixcmp(oitem->path, nitem->path) == 0) { + /* recurse into directory if explicitly requested or + * if there are tracked items inside the directory + */ + if ((diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS) || + (oitem && git__prefixcmp(oitem->path, nitem->path) == 0)) + { if (is_ignored) ignore_prefix = nitem->path; - error = git_iterator_advance_into_directory(new_iter, &nitem); + + if (git_iterator_advance_into_directory(new_iter, &nitem) < 0) + goto fail; + continue; } delta_type = GIT_DELTA_UNTRACKED; @@ -438,36 +572,35 @@ static int diff_from_iterators( else if (new_iter->type == GIT_ITERATOR_WORKDIR) delta_type = GIT_DELTA_UNTRACKED; - error = diff_delta__from_one(diff, delta_type, nitem); - if (error == GIT_SUCCESS) - error = git_iterator_advance(new_iter, &nitem); - continue; + if (diff_delta__from_one(diff, delta_type, nitem) < 0 || + git_iterator_advance(new_iter, &nitem) < 0) + goto fail; } /* otherwise item paths match, so create MODIFIED record * (or ADDED and DELETED pair if type changed) */ - assert(oitem && nitem && strcmp(oitem->path, nitem->path) == 0); + else { + assert(oitem && nitem && strcmp(oitem->path, nitem->path) == 0); - error = maybe_modified(old_iter, oitem, new_iter, nitem, diff); - if (error == GIT_SUCCESS) - error = git_iterator_advance(old_iter, &oitem); - if (error == GIT_SUCCESS) - error = git_iterator_advance(new_iter, &nitem); + if (maybe_modified(old_iter, oitem, new_iter, nitem, diff) < 0 || + git_iterator_advance(old_iter, &oitem) < 0 || + git_iterator_advance(new_iter, &nitem) < 0) + goto fail; + } } -cleanup: git_iterator_free(old_iter); git_iterator_free(new_iter); - - if (error != GIT_SUCCESS) { - git_diff_list_free(diff); - diff = NULL; - } - *diff_ptr = diff; + return 0; - return error; +fail: + git_iterator_free(old_iter); + git_iterator_free(new_iter); + git_diff_list_free(diff); + *diff_ptr = NULL; + return -1; } @@ -478,14 +611,13 @@ int git_diff_tree_to_tree( git_tree *new_tree, git_diff_list **diff) { - int error; git_iterator *a = NULL, *b = NULL; assert(repo && old_tree && new_tree && diff); - if ((error = git_iterator_for_tree(repo, old_tree, &a)) < GIT_SUCCESS || - (error = git_iterator_for_tree(repo, new_tree, &b)) < GIT_SUCCESS) - return error; + if (git_iterator_for_tree(repo, old_tree, &a) < 0 || + git_iterator_for_tree(repo, new_tree, &b) < 0) + return -1; return diff_from_iterators(repo, opts, a, b, diff); } @@ -496,14 +628,13 @@ int git_diff_index_to_tree( git_tree *old_tree, git_diff_list **diff) { - int error; git_iterator *a = NULL, *b = NULL; assert(repo && old_tree && diff); - if ((error = git_iterator_for_tree(repo, old_tree, &a)) < GIT_SUCCESS || - (error = git_iterator_for_index(repo, &b)) < GIT_SUCCESS) - return error; + if (git_iterator_for_tree(repo, old_tree, &a) < 0 || + git_iterator_for_index(repo, &b) < 0) + return -1; return diff_from_iterators(repo, opts, a, b, diff); } @@ -513,14 +644,13 @@ int git_diff_workdir_to_index( const git_diff_options *opts, git_diff_list **diff) { - int error; git_iterator *a = NULL, *b = NULL; assert(repo && diff); - if ((error = git_iterator_for_index(repo, &a)) < GIT_SUCCESS || - (error = git_iterator_for_workdir(repo, &b)) < GIT_SUCCESS) - return error; + if (git_iterator_for_index(repo, &a) < 0 || + git_iterator_for_workdir(repo, &b) < 0) + return -1; return diff_from_iterators(repo, opts, a, b, diff); } @@ -532,14 +662,13 @@ int git_diff_workdir_to_tree( git_tree *old_tree, git_diff_list **diff) { - int error; git_iterator *a = NULL, *b = NULL; assert(repo && old_tree && diff); - if ((error = git_iterator_for_tree(repo, old_tree, &a)) < GIT_SUCCESS || - (error = git_iterator_for_workdir(repo, &b)) < GIT_SUCCESS) - return error; + if (git_iterator_for_tree(repo, old_tree, &a) < 0 || + git_iterator_for_workdir(repo, &b) < 0) + return -1; return diff_from_iterators(repo, opts, a, b, diff); } @@ -548,52 +677,53 @@ int git_diff_merge( git_diff_list *onto, const git_diff_list *from) { - int error; - unsigned int i = 0, j = 0; + int error = 0; + git_pool onto_pool; git_vector onto_new; git_diff_delta *delta; + unsigned int i, j; + + assert(onto && from); - error = git_vector_init(&onto_new, onto->deltas.length, diff_delta__cmp); - if (error < GIT_SUCCESS) - return error; + if (!from->deltas.length) + return 0; - while (i < onto->deltas.length || j < from->deltas.length) { - git_diff_delta *o = git_vector_get(&onto->deltas, i); - const git_diff_delta *f = git_vector_get_const(&from->deltas, j); - const char *opath = - !o ? NULL : o->old_file.path ? o->old_file.path : o->new_file.path; - const char *fpath = - !f ? NULL : f->old_file.path ? f->old_file.path : f->new_file.path; + if (git_vector_init(&onto_new, onto->deltas.length, diff_delta__cmp) < 0 || + git_pool_init(&onto_pool, 1, 0) < 0) + return -1; - if (opath && (!fpath || strcmp(opath, fpath) < 0)) { - delta = diff_delta__dup(o); + for (i = 0, j = 0; i < onto->deltas.length || j < from->deltas.length; ) { + git_diff_delta *o = GIT_VECTOR_GET(&onto->deltas, i); + const git_diff_delta *f = GIT_VECTOR_GET(&from->deltas, j); + int cmp = !f ? -1 : !o ? 1 : strcmp(o->old_file.path, f->old_file.path); + + if (cmp < 0) { + delta = diff_delta__dup(o, &onto_pool); i++; - } else if (fpath && (!opath || strcmp(opath, fpath) > 0)) { - delta = diff_delta__dup(f); + } else if (cmp > 0) { + delta = diff_delta__dup(f, &onto_pool); j++; } else { - delta = diff_delta__merge_like_cgit(o, f); + delta = diff_delta__merge_like_cgit(o, f, &onto_pool); i++; j++; } - if (!delta) - error = GIT_ENOMEM; - else - error = git_vector_insert(&onto_new, delta); - - if (error != GIT_SUCCESS) + if ((error = !delta ? -1 : git_vector_insert(&onto_new, delta)) < 0) break; } - if (error == GIT_SUCCESS) { + if (!error) { git_vector_swap(&onto->deltas, &onto_new); + git_pool_swap(&onto->pool, &onto_pool); onto->new_src = from->new_src; } git_vector_foreach(&onto_new, i, delta) - diff_delta__free(delta); + git__free(delta); git_vector_free(&onto_new); + git_pool_clear(&onto_pool); return error; } + diff --git a/src/diff.h b/src/diff.h index 7d69199ea..ac2457956 100644 --- a/src/diff.h +++ b/src/diff.h @@ -12,13 +12,28 @@ #include "buffer.h" #include "iterator.h" #include "repository.h" +#include "pool.h" + +#define DIFF_OLD_PREFIX_DEFAULT "a/" +#define DIFF_NEW_PREFIX_DEFAULT "b/" + +enum { + GIT_DIFFCAPS_HAS_SYMLINKS = (1 << 0), /* symlinks on platform? */ + GIT_DIFFCAPS_ASSUME_UNCHANGED = (1 << 1), /* use stat? */ + GIT_DIFFCAPS_TRUST_EXEC_BIT = (1 << 2), /* use st_mode exec bit? */ + GIT_DIFFCAPS_TRUST_CTIME = (1 << 3), /* use st_ctime? */ + GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */ +}; struct git_diff_list { git_repository *repo; git_diff_options opts; + git_vector pathspec; git_vector deltas; /* vector of git_diff_file_delta */ + git_pool pool; git_iterator_type_t old_src; git_iterator_type_t new_src; + uint32_t diffcaps; }; #endif diff --git a/src/diff_output.c b/src/diff_output.c index 23f74d216..ca28fd01e 100644 --- a/src/diff_output.c +++ b/src/diff_output.c @@ -19,9 +19,10 @@ typedef struct { git_diff_list *diff; void *cb_data; git_diff_hunk_fn hunk_cb; - git_diff_line_fn line_cb; + git_diff_data_fn line_cb; unsigned int index; git_diff_delta *delta; + git_diff_range range; } diff_output_info; static int read_next_int(const char **str, int *value) @@ -35,31 +36,41 @@ static int read_next_int(const char **str, int *value) v = (v * 10) + (*scan - '0'); *str = scan; *value = v; - return (digits > 0) ? GIT_SUCCESS : GIT_ENOTFOUND; + return (digits > 0) ? 0 : -1; } static int diff_output_cb(void *priv, mmbuffer_t *bufs, int len) { - int err = GIT_SUCCESS; diff_output_info *info = priv; if (len == 1 && info->hunk_cb) { git_diff_range range = { -1, 0, -1, 0 }; + const char *scan = bufs[0].ptr; /* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */ - if (bufs[0].ptr[0] == '@') { - const char *scan = bufs[0].ptr; - if (!(err = read_next_int(&scan, &range.old_start)) && *scan == ',') - err = read_next_int(&scan, &range.old_lines); - if (!err && - !(err = read_next_int(&scan, &range.new_start)) && *scan == ',') - err = read_next_int(&scan, &range.new_lines); - if (!err && range.old_start >= 0 && range.new_start >= 0) - err = info->hunk_cb( - info->cb_data, info->delta, &range, bufs[0].ptr, bufs[0].size); - } + if (*scan != '@') + return -1; + + if (read_next_int(&scan, &range.old_start) < 0) + return -1; + if (*scan == ',' && read_next_int(&scan, &range.old_lines) < 0) + return -1; + + if (read_next_int(&scan, &range.new_start) < 0) + return -1; + if (*scan == ',' && read_next_int(&scan, &range.new_lines) < 0) + return -1; + + if (range.old_start < 0 || range.new_start < 0) + return -1; + + memcpy(&info->range, &range, sizeof(git_diff_range)); + + return info->hunk_cb( + info->cb_data, info->delta, &range, bufs[0].ptr, bufs[0].size); } - else if ((len == 2 || len == 3) && info->line_cb) { + + if ((len == 2 || len == 3) && info->line_cb) { int origin; /* expect " "/"-"/"+", then data, then maybe newline */ @@ -68,41 +79,43 @@ static int diff_output_cb(void *priv, mmbuffer_t *bufs, int len) (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION : GIT_DIFF_LINE_CONTEXT; - err = info->line_cb( - info->cb_data, info->delta, origin, bufs[1].ptr, bufs[1].size); + if (info->line_cb( + info->cb_data, info->delta, &info->range, origin, bufs[1].ptr, bufs[1].size) < 0) + return -1; /* deal with adding and removing newline at EOF */ - if (err == GIT_SUCCESS && len == 3) { + if (len == 3) { if (origin == GIT_DIFF_LINE_ADDITION) origin = GIT_DIFF_LINE_ADD_EOFNL; else origin = GIT_DIFF_LINE_DEL_EOFNL; - err = info->line_cb( - info->cb_data, info->delta, origin, bufs[2].ptr, bufs[2].size); + return info->line_cb( + info->cb_data, info->delta, &info->range, origin, bufs[2].ptr, bufs[2].size); } } - return err; + return 0; } #define BINARY_DIFF_FLAGS (GIT_DIFF_FILE_BINARY|GIT_DIFF_FILE_NOT_BINARY) -static int set_file_is_binary_by_attr(git_repository *repo, git_diff_file *file) +static int update_file_is_binary_by_attr(git_repository *repo, git_diff_file *file) { const char *value; - int error = git_attr_get(repo, file->path, "diff", &value); - if (error != GIT_SUCCESS) - return error; + if (git_attr_get(repo, file->path, "diff", &value) < 0) + return -1; + if (GIT_ATTR_FALSE(value)) file->flags |= GIT_DIFF_FILE_BINARY; else if (GIT_ATTR_TRUE(value)) file->flags |= GIT_DIFF_FILE_NOT_BINARY; /* otherwise leave file->flags alone */ - return error; + + return 0; } -static void set_delta_is_binary(git_diff_delta *delta) +static void update_delta_is_binary(git_diff_delta *delta) { if ((delta->old_file.flags & GIT_DIFF_FILE_BINARY) != 0 || (delta->new_file.flags & GIT_DIFF_FILE_BINARY) != 0) @@ -117,7 +130,7 @@ static int file_is_binary_by_attr( git_diff_list *diff, git_diff_delta *delta) { - int error, mirror_new; + int error = 0, mirror_new; delta->binary = -1; @@ -128,7 +141,7 @@ static int file_is_binary_by_attr( delta->old_file.flags |= GIT_DIFF_FILE_BINARY; delta->new_file.flags |= GIT_DIFF_FILE_BINARY; delta->binary = 1; - return GIT_SUCCESS; + return 0; } /* check if user is forcing us to text diff these files */ @@ -136,22 +149,21 @@ static int file_is_binary_by_attr( delta->old_file.flags |= GIT_DIFF_FILE_NOT_BINARY; delta->new_file.flags |= GIT_DIFF_FILE_NOT_BINARY; delta->binary = 0; - return GIT_SUCCESS; + return 0; } /* check diff attribute +, -, or 0 */ - error = set_file_is_binary_by_attr(diff->repo, &delta->old_file); - if (error != GIT_SUCCESS) - return error; + if (update_file_is_binary_by_attr(diff->repo, &delta->old_file) < 0) + return -1; mirror_new = (delta->new_file.path == delta->old_file.path || strcmp(delta->new_file.path, delta->old_file.path) == 0); if (mirror_new) delta->new_file.flags &= (delta->old_file.flags & BINARY_DIFF_FLAGS); else - error = set_file_is_binary_by_attr(diff->repo, &delta->new_file); + error = update_file_is_binary_by_attr(diff->repo, &delta->new_file); - set_delta_is_binary(delta); + update_delta_is_binary(delta); return error; } @@ -191,11 +203,11 @@ static int file_is_binary_by_content( delta->new_file.flags |= GIT_DIFF_FILE_NOT_BINARY; } - set_delta_is_binary(delta); + update_delta_is_binary(delta); /* TODO: if value != NULL, implement diff drivers */ - return GIT_SUCCESS; + return 0; } static void setup_xdiff_options( @@ -226,17 +238,15 @@ static int get_blob_content( git_map *map, git_blob **blob) { - int error; - if (git_oid_iszero(oid)) - return GIT_SUCCESS; + return 0; - if ((error = git_blob_lookup(blob, repo, oid)) == GIT_SUCCESS) { - map->data = (void *)git_blob_rawcontent(*blob); - map->len = git_blob_rawsize(*blob); - } + if (git_blob_lookup(blob, repo, oid) < 0) + return -1; - return error; + map->data = (void *)git_blob_rawcontent(*blob); + map->len = git_blob_rawsize(*blob); + return 0; } static int get_workdir_content( @@ -244,35 +254,33 @@ static int get_workdir_content( git_diff_file *file, git_map *map) { - git_buf full_path = GIT_BUF_INIT; - int error = git_buf_joinpath( - &full_path, git_repository_workdir(repo), file->path); - if (error != GIT_SUCCESS) - return error; + int error = 0; + git_buf path = GIT_BUF_INIT; + + if (git_buf_joinpath(&path, git_repository_workdir(repo), file->path) < 0) + return -1; if (S_ISLNK(file->mode)) { + ssize_t read_len; + file->flags |= GIT_DIFF_FILE_FREE_DATA; file->flags |= GIT_DIFF_FILE_BINARY; map->data = git__malloc((size_t)file->size + 1); - if (map->data == NULL) - error = GIT_ENOMEM; - else { - ssize_t read_len = - p_readlink(full_path.ptr, map->data, (size_t)file->size + 1); - if (read_len != (ssize_t)file->size) - error = git__throw( - GIT_EOSERR, "Failed to read symlink %s", file->path); - else - map->len = read_len; - - } + GITERR_CHECK_ALLOC(map->data); + + read_len = p_readlink(path.ptr, map->data, (size_t)file->size + 1); + if (read_len != (ssize_t)file->size) { + giterr_set(GITERR_OS, "Failed to read symlink '%s'", file->path); + error = -1; + } else + map->len = read_len; } else { - error = git_futils_mmap_ro_file(map, full_path.ptr); + error = git_futils_mmap_ro_file(map, path.ptr); file->flags |= GIT_DIFF_FILE_UNMAP_DATA; } - git_buf_free(&full_path); + git_buf_free(&path); return error; } @@ -298,9 +306,9 @@ int git_diff_foreach( void *data, git_diff_file_fn file_cb, git_diff_hunk_fn hunk_cb, - git_diff_line_fn line_cb) + git_diff_data_fn line_cb) { - int error = GIT_SUCCESS; + int error = 0; diff_output_info info; git_diff_delta *delta; xpparam_t xdiff_params; @@ -322,7 +330,8 @@ int git_diff_foreach( git_map old_data, new_data; mmfile_t old_xdiff_data, new_xdiff_data; - if (delta->status == GIT_DELTA_UNMODIFIED) + if (delta->status == GIT_DELTA_UNMODIFIED && + (diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) continue; if (delta->status == GIT_DELTA_IGNORED && @@ -333,8 +342,7 @@ int git_diff_foreach( (diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) continue; - error = file_is_binary_by_attr(diff, delta); - if (error < GIT_SUCCESS) + if ((error = file_is_binary_by_attr(diff, delta)) < 0) goto cleanup; old_data.data = ""; @@ -358,7 +366,8 @@ int git_diff_foreach( else error = get_blob_content( diff->repo, &delta->old_file.oid, &old_data, &old_blob); - if (error != GIT_SUCCESS) + + if (error < 0) goto cleanup; } @@ -372,13 +381,15 @@ int git_diff_foreach( else error = get_blob_content( diff->repo, &delta->new_file.oid, &new_data, &new_blob); - if (error != GIT_SUCCESS) + + if (error < 0) goto cleanup; if ((delta->new_file.flags | GIT_DIFF_FILE_VALID_OID) == 0) { error = git_odb_hash( &delta->new_file.oid, new_data.data, new_data.len, GIT_OBJ_BLOB); - if (error != GIT_SUCCESS) + + if (error < 0) goto cleanup; /* since we did not have the definitive oid, we may have @@ -386,7 +397,8 @@ int git_diff_foreach( */ if (git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid) == 0) { delta->status = GIT_DELTA_UNMODIFIED; - goto cleanup; + if ((diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) + goto cleanup; } } } @@ -397,7 +409,7 @@ int git_diff_foreach( if (delta->binary == -1) { error = file_is_binary_by_content( diff, delta, &old_data, &new_data); - if (error < GIT_SUCCESS) + if (error < 0) goto cleanup; } @@ -407,7 +419,7 @@ int git_diff_foreach( if (file_cb != NULL) { error = file_cb(data, delta, (float)info.index / diff->deltas.length); - if (error != GIT_SUCCESS) + if (error < 0) goto cleanup; } @@ -434,7 +446,7 @@ cleanup: release_content(&delta->old_file, &old_data, old_blob); release_content(&delta->new_file, &new_data, new_blob); - if (error != GIT_SUCCESS) + if (error < 0) break; } @@ -444,7 +456,7 @@ cleanup: typedef struct { git_diff_list *diff; - git_diff_output_fn print_cb; + git_diff_data_fn print_cb; void *cb_data; git_buf *buf; } diff_print_info; @@ -479,7 +491,7 @@ static int print_compact(void *data, git_diff_delta *delta, float progress) } if (!code) - return GIT_SUCCESS; + return 0; old_suffix = pick_suffix(delta->old_file.mode); new_suffix = pick_suffix(delta->new_file.mode); @@ -499,16 +511,16 @@ static int print_compact(void *data, git_diff_delta *delta, float progress) else git_buf_printf(pi->buf, "%c\t%s\n", code, delta->old_file.path); - if (git_buf_lasterror(pi->buf) != GIT_SUCCESS) - return git_buf_lasterror(pi->buf); + if (git_buf_oom(pi->buf)) + return -1; - return pi->print_cb(pi->cb_data, GIT_DIFF_LINE_FILE_HDR, pi->buf->ptr); + return pi->print_cb(pi->cb_data, delta, NULL, GIT_DIFF_LINE_FILE_HDR, git_buf_cstr(pi->buf), git_buf_len(pi->buf)); } int git_diff_print_compact( git_diff_list *diff, void *cb_data, - git_diff_output_fn print_cb) + git_diff_data_fn print_cb) { int error; git_buf buf = GIT_BUF_INIT; @@ -551,32 +563,42 @@ static int print_oid_range(diff_print_info *pi, git_diff_delta *delta) git_buf_printf(pi->buf, "index %s..%s\n", start_oid, end_oid); } - return git_buf_lasterror(pi->buf); + if (git_buf_oom(pi->buf)) + return -1; + + return 0; } static int print_patch_file(void *data, git_diff_delta *delta, float progress) { - int error; diff_print_info *pi = data; const char *oldpfx = pi->diff->opts.old_prefix; const char *oldpath = delta->old_file.path; const char *newpfx = pi->diff->opts.new_prefix; const char *newpath = delta->new_file.path; + int result; GIT_UNUSED(progress); + if (!oldpfx) + oldpfx = DIFF_OLD_PREFIX_DEFAULT; + + if (!newpfx) + newpfx = DIFF_NEW_PREFIX_DEFAULT; + git_buf_clear(pi->buf); git_buf_printf(pi->buf, "diff --git %s%s %s%s\n", oldpfx, delta->old_file.path, newpfx, delta->new_file.path); - if ((error = print_oid_range(pi, delta)) < GIT_SUCCESS) - return error; + + if (print_oid_range(pi, delta) < 0) + return -1; if (git_oid_iszero(&delta->old_file.oid)) { oldpfx = ""; oldpath = "/dev/null"; } if (git_oid_iszero(&delta->new_file.oid)) { - oldpfx = ""; - oldpath = "/dev/null"; + newpfx = ""; + newpath = "/dev/null"; } if (delta->binary != 1) { @@ -584,21 +606,24 @@ static int print_patch_file(void *data, git_diff_delta *delta, float progress) git_buf_printf(pi->buf, "+++ %s%s\n", newpfx, newpath); } - if (git_buf_lasterror(pi->buf) != GIT_SUCCESS) - return git_buf_lasterror(pi->buf); + if (git_buf_oom(pi->buf)) + return -1; - error = pi->print_cb(pi->cb_data, GIT_DIFF_LINE_FILE_HDR, pi->buf->ptr); - if (error != GIT_SUCCESS || delta->binary != 1) - return error; + result = pi->print_cb(pi->cb_data, delta, NULL, GIT_DIFF_LINE_FILE_HDR, git_buf_cstr(pi->buf), git_buf_len(pi->buf)); + if (result < 0) + return result; + + if (delta->binary != 1) + return 0; git_buf_clear(pi->buf); git_buf_printf( pi->buf, "Binary files %s%s and %s%s differ\n", oldpfx, oldpath, newpfx, newpath); - if (git_buf_lasterror(pi->buf) != GIT_SUCCESS) - return git_buf_lasterror(pi->buf); + if (git_buf_oom(pi->buf)) + return -1; - return pi->print_cb(pi->cb_data, GIT_DIFF_LINE_BINARY, pi->buf->ptr); + return pi->print_cb(pi->cb_data, delta, NULL, GIT_DIFF_LINE_BINARY, git_buf_cstr(pi->buf), git_buf_len(pi->buf)); } static int print_patch_hunk( @@ -610,28 +635,23 @@ static int print_patch_hunk( { diff_print_info *pi = data; - GIT_UNUSED(d); - GIT_UNUSED(r); - git_buf_clear(pi->buf); + if (git_buf_printf(pi->buf, "%.*s", (int)header_len, header) < 0) + return -1; - if (git_buf_printf(pi->buf, "%.*s", (int)header_len, header) == GIT_SUCCESS) - return pi->print_cb(pi->cb_data, GIT_DIFF_LINE_HUNK_HDR, pi->buf->ptr); - else - return git_buf_lasterror(pi->buf); + return pi->print_cb(pi->cb_data, d, r, GIT_DIFF_LINE_HUNK_HDR, git_buf_cstr(pi->buf), git_buf_len(pi->buf)); } static int print_patch_line( void *data, git_diff_delta *delta, + git_diff_range *range, char line_origin, /* GIT_DIFF_LINE value from above */ const char *content, size_t content_len) { diff_print_info *pi = data; - GIT_UNUSED(delta); - git_buf_clear(pi->buf); if (line_origin == GIT_DIFF_LINE_ADDITION || @@ -641,16 +661,16 @@ static int print_patch_line( else if (content_len > 0) git_buf_printf(pi->buf, "%.*s", (int)content_len, content); - if (git_buf_lasterror(pi->buf) != GIT_SUCCESS) - return git_buf_lasterror(pi->buf); + if (git_buf_oom(pi->buf)) + return -1; - return pi->print_cb(pi->cb_data, line_origin, pi->buf->ptr); + return pi->print_cb(pi->cb_data, delta, range, line_origin, git_buf_cstr(pi->buf), git_buf_len(pi->buf)); } int git_diff_print_patch( git_diff_list *diff, void *cb_data, - git_diff_output_fn print_cb) + git_diff_data_fn print_cb) { int error; git_buf buf = GIT_BUF_INIT; @@ -676,7 +696,7 @@ int git_diff_blobs( git_diff_options *options, void *cb_data, git_diff_hunk_fn hunk_cb, - git_diff_line_fn line_cb) + git_diff_data_fn line_cb) { diff_output_info info; git_diff_delta delta; @@ -732,5 +752,5 @@ int git_diff_blobs( xdl_diff(&old_data, &new_data, &xdiff_params, &xdiff_config, &xdiff_callback); - return GIT_SUCCESS; + return 0; } diff --git a/src/errors.c b/src/errors.c index 58e0976f2..7a6bbd654 100644 --- a/src/errors.c +++ b/src/errors.c @@ -6,6 +6,7 @@ */ #include "common.h" #include "global.h" +#include "posix.h" #include <stdarg.h> static struct { @@ -39,7 +40,7 @@ static struct { {GIT_EEXISTS, "A reference with this name already exists"}, {GIT_EOVERFLOW, "The given integer literal is too large to be parsed"}, {GIT_ENOTNUM, "The given literal is not a valid number"}, - {GIT_EAMBIGUOUSOIDPREFIX, "The given oid prefix is ambiguous"}, + {GIT_EAMBIGUOUS, "The given oid prefix is ambiguous"}, }; const char *git_strerror(int num) @@ -91,8 +92,12 @@ const char *git_lasterror(void) { char *last_error = GIT_GLOBAL->error.last; - if (!last_error[0]) + if (!last_error[0]) { + const git_error *err = git_error_last(); + if (err != NULL) + return err->message; return NULL; + } return last_error; } @@ -102,3 +107,104 @@ void git_clearerror(void) char *last_error = GIT_GLOBAL->error.last; last_error[0] = '\0'; } + +/******************************************** + * New error handling + ********************************************/ + +static git_error g_git_oom_error = { + "Out of memory", + GITERR_NOMEMORY +}; + +void giterr_set_oom(void) +{ + GIT_GLOBAL->last_error = &g_git_oom_error; +} + +void giterr_set(int error_class, const char *string, ...) +{ + char error_str[1024]; + va_list arglist; + + /* Grab errno before calling vsnprintf() so it won't be overwritten */ + const char *os_error_msg = + (error_class == GITERR_OS && errno != 0) ? strerror(errno) : NULL; +#ifdef GIT_WIN32 + DWORD dwLastError = GetLastError(); +#endif + + va_start(arglist, string); + p_vsnprintf(error_str, sizeof(error_str), string, arglist); + va_end(arglist); + + /* automatically suffix strerror(errno) for GITERR_OS errors */ + if (error_class == GITERR_OS) { + if (os_error_msg != NULL) { + strncat(error_str, ": ", sizeof(error_str)); + strncat(error_str, os_error_msg, sizeof(error_str)); + errno = 0; /* reset so same error won't be reported twice */ + } +#ifdef GIT_WIN32 + else if (dwLastError != 0) { + LPVOID lpMsgBuf = NULL; + + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwLastError, 0, (LPTSTR) &lpMsgBuf, 0, NULL); + + if (lpMsgBuf) { + strncat(error_str, ": ", sizeof(error_str)); + strncat(error_str, (const char *)lpMsgBuf, sizeof(error_str)); + LocalFree(lpMsgBuf); + } + + SetLastError(0); + } +#endif + } + + giterr_set_str(error_class, error_str); +} + +void giterr_set_str(int error_class, const char *string) +{ + git_error *error = &GIT_GLOBAL->error_t; + + git__free(error->message); + + error->message = git__strdup(string); + error->klass = error_class; + + if (error->message == NULL) { + giterr_set_oom(); + return; + } + + GIT_GLOBAL->last_error = error; +} + +void giterr_set_regex(const regex_t *regex, int error_code) +{ + char error_buf[1024]; + regerror(error_code, regex, error_buf, sizeof(error_buf)); + giterr_set_str(GITERR_REGEX, error_buf); +} + +void giterr_clear(void) +{ + GIT_GLOBAL->last_error = NULL; +} + +const git_error *git_error_last(void) +{ + return GIT_GLOBAL->last_error; +} + +void git_error_clear(void) +{ + giterr_clear(); +} + diff --git a/src/fetch.c b/src/fetch.c index 23208f17e..6fe1b5676 100644 --- a/src/fetch.c +++ b/src/fetch.c @@ -9,6 +9,7 @@ #include "git2/oid.h" #include "git2/refs.h" #include "git2/revwalk.h" +#include "git2/indexer.h" #include "common.h" #include "transport.h" @@ -28,19 +29,21 @@ struct filter_payload { static int filter_ref__cb(git_remote_head *head, void *payload) { struct filter_payload *p = payload; - int error; + int ret; if (!p->found_head && strcmp(head->name, GIT_HEAD_FILE) == 0) { p->found_head = 1; } else { /* If it doesn't match the refpec, we don't want it */ - error = git_refspec_src_match(p->spec, head->name); + ret = git_refspec_src_match(p->spec, head->name); - if (error == GIT_ENOMATCH) - return GIT_SUCCESS; + if (ret == GIT_ENOMATCH) + return 0; - if (error < GIT_SUCCESS) - return git__rethrow(error, "Error matching remote ref name"); + if (ret < GIT_SUCCESS) { + giterr_set(GITERR_NET, "Error matching remote ref name"); + return -1; + } } /* If we have the object, mark it so we don't ask for it */ @@ -54,7 +57,6 @@ static int filter_ref__cb(git_remote_head *head, void *payload) static int filter_wants(git_remote *remote) { - int error; struct filter_payload p; git_vector_clear(&remote->refs); @@ -69,9 +71,8 @@ static int filter_wants(git_remote *remote) p.found_head = 0; p.remote = remote; - error = git_repository_odb__weakptr(&p.odb, remote->repo); - if (error < GIT_SUCCESS) - return error; + if (git_repository_odb__weakptr(&p.odb, remote->repo) < 0) + return -1; return remote->transport->ls(remote->transport, &filter_ref__cb, &p); } @@ -83,19 +84,16 @@ static int filter_wants(git_remote *remote) */ int git_fetch_negotiate(git_remote *remote) { - int error; git_transport *t = remote->transport; - error = filter_wants(remote); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to filter the reference list for wants"); + if (filter_wants(remote) < 0) { + giterr_set(GITERR_NET, "Failed to filter the reference list for wants"); + return -1; + } /* Don't try to negotiate when we don't want anything */ - if (remote->refs.length == 0) - return GIT_SUCCESS; - - if (!remote->need_pack) - return GIT_SUCCESS; + if (remote->refs.length == 0 || !remote->need_pack) + return 0; /* * Now we have everything set up so we can start tell the server @@ -104,75 +102,103 @@ int git_fetch_negotiate(git_remote *remote) return t->negotiate_fetch(t, remote->repo, &remote->refs); } -int git_fetch_download_pack(char **out, git_remote *remote) +int git_fetch_download_pack(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats) { - if(!remote->need_pack) { - *out = NULL; - return GIT_SUCCESS; - } + if(!remote->need_pack) + return 0; - return remote->transport->download_pack(out, remote->transport, remote->repo); + return remote->transport->download_pack(remote->transport, remote->repo, bytes, stats); } /* Receiving data from a socket and storing it is pretty much the same for git and HTTP */ int git_fetch__download_pack( - char **out, const char *buffered, size_t buffered_size, GIT_SOCKET fd, - git_repository *repo) + git_repository *repo, + git_off_t *bytes, + git_indexer_stats *stats) { - git_filebuf file = GIT_FILEBUF_INIT; - int error; + int recvd; char buff[1024]; - git_buf path = GIT_BUF_INIT; - static const char suff[] = "/objects/pack/pack-received"; gitno_buffer buf; + git_indexer_stream *idx; gitno_buffer_setup(&buf, buff, sizeof(buff), fd); if (memcmp(buffered, "PACK", strlen("PACK"))) { - return git__throw(GIT_ERROR, "The pack doesn't start with the signature"); + giterr_set(GITERR_NET, "The pack doesn't start with the signature"); + return -1; } - error = git_buf_joinpath(&path, repo->path_repository, suff); - if (error < GIT_SUCCESS) - goto cleanup; + if (git_indexer_stream_new(&idx, git_repository_path(repo)) < 0) + return -1; - error = git_filebuf_open(&file, path.ptr, GIT_FILEBUF_TEMPORARY); - if (error < GIT_SUCCESS) - goto cleanup; + memset(stats, 0, sizeof(git_indexer_stats)); + if (git_indexer_stream_add(idx, buffered, buffered_size, stats) < 0) + goto on_error; - /* Part of the packfile has been received, don't loose it */ - error = git_filebuf_write(&file, buffered, buffered_size); - if (error < GIT_SUCCESS) - goto cleanup; + *bytes = buffered_size; - while (1) { - error = git_filebuf_write(&file, buf.data, buf.offset); - if (error < GIT_SUCCESS) - goto cleanup; + do { + if (git_indexer_stream_add(idx, buf.data, buf.offset, stats) < 0) + goto on_error; gitno_consume_n(&buf, buf.offset); - error = gitno_recv(&buf); - if (error < GIT_SUCCESS) - goto cleanup; - if (error == 0) /* Orderly shutdown */ - break; - } + if ((recvd = gitno_recv(&buf)) < 0) + goto on_error; + + *bytes += recvd; + } while(recvd > 0); + + if (git_indexer_stream_finalize(idx, stats)) + goto on_error; + + git_indexer_stream_free(idx); + return 0; + +on_error: + git_indexer_stream_free(idx); + return -1; +} + +int git_fetch_setup_walk(git_revwalk **out, git_repository *repo) +{ + git_revwalk *walk; + git_strarray refs; + unsigned int i; + git_reference *ref; + + if (git_reference_listall(&refs, repo, GIT_REF_LISTALL) < 0) + return -1; + + if (git_revwalk_new(&walk, repo) < 0) + return -1; + + git_revwalk_sorting(walk, GIT_SORT_TIME); + + for (i = 0; i < refs.count; ++i) { + /* No tags */ + if (!git__prefixcmp(refs.strings[i], GIT_REFS_TAGS_DIR)) + continue; + + if (git_reference_lookup(&ref, repo, refs.strings[i]) < 0) + goto on_error; + + if (git_reference_type(ref) == GIT_REF_SYMBOLIC) + continue; + if (git_revwalk_push(walk, git_reference_oid(ref)) < 0) + goto on_error; - *out = git__strdup(file.path_lock); - if (*out == NULL) { - error = GIT_ENOMEM; - goto cleanup; + git_reference_free(ref); } - /* A bit dodgy, but we need to keep the pack at the temporary path */ - error = git_filebuf_commit_at(&file, file.path_lock, GIT_PACK_FILE_MODE); -cleanup: - if (error < GIT_SUCCESS) - git_filebuf_cleanup(&file); - git_buf_free(&path); + git_strarray_free(&refs); + *out = walk; + return 0; - return error; +on_error: + git_reference_free(ref); + git_strarray_free(&refs); + return -1; } diff --git a/src/fetch.h b/src/fetch.h index c1ab84034..b3192a563 100644 --- a/src/fetch.h +++ b/src/fetch.h @@ -10,9 +10,10 @@ #include "netops.h" int git_fetch_negotiate(git_remote *remote); -int git_fetch_download_pack(char **out, git_remote *remote); +int git_fetch_download_pack(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats); -int git_fetch__download_pack(char **out, const char *buffered, size_t buffered_size, - GIT_SOCKET fd, git_repository *repo); +int git_fetch__download_pack(const char *buffered, size_t buffered_size, GIT_SOCKET fd, + git_repository *repo, git_off_t *bytes, git_indexer_stats *stats); +int git_fetch_setup_walk(git_revwalk **out, git_repository *repo); #endif diff --git a/src/filebuf.c b/src/filebuf.c index 01df8e2d0..6538aea66 100644 --- a/src/filebuf.c +++ b/src/filebuf.c @@ -14,13 +14,46 @@ static const size_t WRITE_BUFFER_SIZE = (4096 * 2); +enum buferr_t { + BUFERR_OK = 0, + BUFERR_WRITE, + BUFERR_ZLIB, + BUFERR_MEM +}; + +#define ENSURE_BUF_OK(buf) if ((buf)->last_error != BUFERR_OK) { return -1; } + +static int verify_last_error(git_filebuf *file) +{ + switch (file->last_error) { + case BUFERR_WRITE: + giterr_set(GITERR_OS, "Failed to write out file"); + return -1; + + case BUFERR_MEM: + giterr_set_oom(); + return -1; + + case BUFERR_ZLIB: + giterr_set(GITERR_ZLIB, + "Buffer error when writing out ZLib data"); + return -1; + + default: + return 0; + } +} + static int lock_file(git_filebuf *file, int flags) { - if (git_path_exists(file->path_lock) == 0) { + if (git_path_exists(file->path_lock) == true) { if (flags & GIT_FILEBUF_FORCE) p_unlink(file->path_lock); - else - return git__throw(GIT_EOSERR, "Failed to lock file"); + else { + giterr_set(GITERR_OS, + "Failed to lock file '%s' for writing", file->path_lock); + return -1; + } } /* create path to the file buffer is required */ @@ -32,16 +65,22 @@ static int lock_file(git_filebuf *file, int flags) } if (file->fd < 0) - return git__throw(GIT_EOSERR, "Failed to create lock"); + return -1; + + file->fd_is_open = true; - if ((flags & GIT_FILEBUF_APPEND) && git_path_exists(file->path_original) == 0) { + if ((flags & GIT_FILEBUF_APPEND) && git_path_exists(file->path_original) == true) { git_file source; char buffer[2048]; size_t read_bytes; source = p_open(file->path_original, O_RDONLY); - if (source < 0) - return git__throw(GIT_EOSERR, "Failed to lock file. Could not open %s", file->path_original); + if (source < 0) { + giterr_set(GITERR_OS, + "Failed to open file '%s' for reading", + file->path_original); + return -1; + } while ((read_bytes = p_read(source, buffer, 2048)) > 0) { p_write(file->fd, buffer, read_bytes); @@ -52,15 +91,15 @@ static int lock_file(git_filebuf *file, int flags) p_close(source); } - return GIT_SUCCESS; + return 0; } void git_filebuf_cleanup(git_filebuf *file) { - if (file->fd >= 0) + if (file->fd_is_open && file->fd >= 0) p_close(file->fd); - if (file->fd >= 0 && file->path_lock && git_path_exists(file->path_lock) == GIT_SUCCESS) + if (file->fd_is_open && file->path_lock && git_path_exists(file->path_lock)) p_unlink(file->path_lock); if (file->digest) @@ -93,20 +132,21 @@ GIT_INLINE(int) flush_buffer(git_filebuf *file) static int write_normal(git_filebuf *file, void *source, size_t len) { - int result = 0; - if (len > 0) { - result = p_write(file->fd, (void *)source, len); + if (p_write(file->fd, (void *)source, len) < 0) { + file->last_error = BUFERR_WRITE; + return -1; + } + if (file->digest) git_hash_update(file->digest, source, len); } - return result; + return 0; } static int write_deflate(git_filebuf *file, void *source, size_t len) { - int result = Z_OK; z_stream *zs = &file->zs; if (len > 0 || file->flush_mode == Z_FINISH) { @@ -119,14 +159,17 @@ static int write_deflate(git_filebuf *file, void *source, size_t len) zs->next_out = file->z_buf; zs->avail_out = (uInt)file->buf_size; - result = deflate(zs, file->flush_mode); - if (result == Z_STREAM_ERROR) - return git__throw(GIT_ERROR, "Failed to deflate input"); + if (deflate(zs, file->flush_mode) == Z_STREAM_ERROR) { + file->last_error = BUFERR_ZLIB; + return -1; + } have = file->buf_size - (size_t)zs->avail_out; - if (p_write(file->fd, file->z_buf, have) < GIT_SUCCESS) - return git__throw(GIT_EOSERR, "Failed to write to file"); + if (p_write(file->fd, file->z_buf, have) < 0) { + file->last_error = BUFERR_WRITE; + return -1; + } } while (zs->avail_out == 0); @@ -136,38 +179,39 @@ static int write_deflate(git_filebuf *file, void *source, size_t len) git_hash_update(file->digest, source, len); } - return GIT_SUCCESS; + return 0; } int git_filebuf_open(git_filebuf *file, const char *path, int flags) { - int error, compression; + int compression; size_t path_len; - assert(file && path); - - if (file->buffer) - return git__throw(GIT_EINVALIDARGS, "Tried to reopen an open filebuf"); + /* opening an already open buffer is a programming error; + * assert that this never happens instead of returning + * an error code */ + assert(file && path && file->buffer == NULL); memset(file, 0x0, sizeof(git_filebuf)); + if (flags & GIT_FILEBUF_DO_NOT_BUFFER) + file->do_not_buffer = true; + file->buf_size = WRITE_BUFFER_SIZE; file->buf_pos = 0; file->fd = -1; + file->last_error = BUFERR_OK; /* Allocate the main cache buffer */ - file->buffer = git__malloc(file->buf_size); - if (file->buffer == NULL){ - error = GIT_ENOMEM; - goto cleanup; + if (!file->do_not_buffer) { + file->buffer = git__malloc(file->buf_size); + GITERR_CHECK_ALLOC(file->buffer); } /* If we are hashing on-write, allocate a new hash context */ if (flags & GIT_FILEBUF_HASH_CONTENTS) { - if ((file->digest = git_hash_new_ctx()) == NULL) { - error = GIT_ENOMEM; - goto cleanup; - } + file->digest = git_hash_new_ctx(); + GITERR_CHECK_ALLOC(file->digest); } compression = flags >> GIT_FILEBUF_DEFLATE_SHIFT; @@ -176,16 +220,13 @@ int git_filebuf_open(git_filebuf *file, const char *path, int flags) if (compression != 0) { /* Initialize the ZLib stream */ if (deflateInit(&file->zs, compression) != Z_OK) { - error = git__throw(GIT_EZLIB, "Failed to initialize zlib"); + giterr_set(GITERR_ZLIB, "Failed to initialize zlib"); goto cleanup; } /* Allocate the Zlib cache buffer */ file->z_buf = git__malloc(file->buf_size); - if (file->z_buf == NULL){ - error = GIT_ENOMEM; - goto cleanup; - } + GITERR_CHECK_ALLOC(file->z_buf); /* Never flush */ file->flush_mode = Z_NO_FLUSH; @@ -200,106 +241,101 @@ int git_filebuf_open(git_filebuf *file, const char *path, int flags) /* Open the file as temporary for locking */ file->fd = git_futils_mktmp(&tmp_path, path); + if (file->fd < 0) { git_buf_free(&tmp_path); - error = GIT_EOSERR; goto cleanup; } + file->fd_is_open = true; /* No original path */ file->path_original = NULL; file->path_lock = git_buf_detach(&tmp_path); - - if (file->path_lock == NULL) { - error = GIT_ENOMEM; - goto cleanup; - } + GITERR_CHECK_ALLOC(file->path_lock); } else { path_len = strlen(path); /* Save the original path of the file */ file->path_original = git__strdup(path); - if (file->path_original == NULL) { - error = GIT_ENOMEM; - goto cleanup; - } + GITERR_CHECK_ALLOC(file->path_original); /* create the locking path by appending ".lock" to the original */ file->path_lock = git__malloc(path_len + GIT_FILELOCK_EXTLENGTH); - if (file->path_lock == NULL) { - error = GIT_ENOMEM; - goto cleanup; - } + GITERR_CHECK_ALLOC(file->path_lock); memcpy(file->path_lock, file->path_original, path_len); memcpy(file->path_lock + path_len, GIT_FILELOCK_EXTENSION, GIT_FILELOCK_EXTLENGTH); /* open the file for locking */ - if ((error = lock_file(file, flags)) < GIT_SUCCESS) + if (lock_file(file, flags) < 0) goto cleanup; } - return GIT_SUCCESS; + return 0; cleanup: git_filebuf_cleanup(file); - return git__rethrow(error, "Failed to open file buffer for '%s'", path); + return -1; } int git_filebuf_hash(git_oid *oid, git_filebuf *file) { - int error; - assert(oid && file && file->digest); - if ((error = flush_buffer(file)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to get hash for file"); + flush_buffer(file); + + if (verify_last_error(file) < 0) + return -1; git_hash_final(oid, file->digest); git_hash_free_ctx(file->digest); file->digest = NULL; - return GIT_SUCCESS; + return 0; } int git_filebuf_commit_at(git_filebuf *file, const char *path, mode_t mode) { git__free(file->path_original); file->path_original = git__strdup(path); - if (file->path_original == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(file->path_original); return git_filebuf_commit(file, mode); } int git_filebuf_commit(git_filebuf *file, mode_t mode) { - int error; - /* temporary files cannot be committed */ assert(file && file->path_original); file->flush_mode = Z_FINISH; - if ((error = flush_buffer(file)) < GIT_SUCCESS) - goto cleanup; + flush_buffer(file); + + if (verify_last_error(file) < 0) + goto on_error; p_close(file->fd); file->fd = -1; + file->fd_is_open = false; if (p_chmod(file->path_lock, mode)) { - error = git__throw(GIT_EOSERR, "Failed to chmod locked file before committing"); - goto cleanup; + giterr_set(GITERR_OS, "Failed to set attributes for file at '%s'", file->path_lock); + goto on_error; } p_unlink(file->path_original); - error = p_rename(file->path_lock, file->path_original); + if (p_rename(file->path_lock, file->path_original) < 0) { + giterr_set(GITERR_OS, "Failed to rename lockfile to '%s'", file->path_original); + goto on_error; + } + + git_filebuf_cleanup(file); + return 0; -cleanup: +on_error: git_filebuf_cleanup(file); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to commit locked file from buffer"); - return GIT_SUCCESS; + return -1; } GIT_INLINE(void) add_to_cache(git_filebuf *file, const void *buf, size_t len) @@ -310,22 +346,25 @@ GIT_INLINE(void) add_to_cache(git_filebuf *file, const void *buf, size_t len) int git_filebuf_write(git_filebuf *file, const void *buff, size_t len) { - int error; const unsigned char *buf = buff; + ENSURE_BUF_OK(file); + + if (file->do_not_buffer) + return file->write(file, (void *)buff, len); + for (;;) { size_t space_left = file->buf_size - file->buf_pos; /* cache if it's small */ if (space_left > len) { add_to_cache(file, buf, len); - return GIT_SUCCESS; + return 0; } add_to_cache(file, buf, space_left); - - if ((error = flush_buffer(file)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to write to buffer"); + if (flush_buffer(file) < 0) + return -1; len -= space_left; buf += space_left; @@ -334,32 +373,37 @@ int git_filebuf_write(git_filebuf *file, const void *buff, size_t len) int git_filebuf_reserve(git_filebuf *file, void **buffer, size_t len) { - int error; size_t space_left = file->buf_size - file->buf_pos; *buffer = NULL; - if (len > file->buf_size) - return GIT_ENOMEM; + ENSURE_BUF_OK(file); + + if (len > file->buf_size) { + file->last_error = BUFERR_MEM; + return -1; + } if (space_left <= len) { - if ((error = flush_buffer(file)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to reserve buffer"); + if (flush_buffer(file) < 0) + return -1; } *buffer = (file->buffer + file->buf_pos); file->buf_pos += len; - return GIT_SUCCESS; + return 0; } int git_filebuf_printf(git_filebuf *file, const char *format, ...) { va_list arglist; size_t space_left; - int len, error; + int len, res; char *tmp_buffer; + ENSURE_BUF_OK(file); + space_left = file->buf_size - file->buf_pos; do { @@ -367,24 +411,28 @@ int git_filebuf_printf(git_filebuf *file, const char *format, ...) len = p_vsnprintf((char *)file->buffer + file->buf_pos, space_left, format, arglist); va_end(arglist); - if (len < 0) - return git__throw(GIT_EOSERR, "Failed to format string"); + if (len < 0) { + file->last_error = BUFERR_MEM; + return -1; + } if ((size_t)len + 1 <= space_left) { file->buf_pos += len; - return GIT_SUCCESS; + return 0; } - if ((error = flush_buffer(file)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to output to buffer"); + if (flush_buffer(file) < 0) + return -1; space_left = file->buf_size - file->buf_pos; } while ((size_t)len + 1 <= space_left); tmp_buffer = git__malloc(len + 1); - if (!tmp_buffer) - return GIT_ENOMEM; + if (!tmp_buffer) { + file->last_error = BUFERR_MEM; + return -1; + } va_start(arglist, format); len = p_vsnprintf(tmp_buffer, len + 1, format, arglist); @@ -392,12 +440,13 @@ int git_filebuf_printf(git_filebuf *file, const char *format, ...) if (len < 0) { git__free(tmp_buffer); - return git__throw(GIT_EOSERR, "Failed to format string"); + file->last_error = BUFERR_MEM; + return -1; } - error = git_filebuf_write(file, tmp_buffer, len); + res = git_filebuf_write(file, tmp_buffer, len); git__free(tmp_buffer); - return error; + return res; } diff --git a/src/filebuf.h b/src/filebuf.h index 371215391..72563b57a 100644 --- a/src/filebuf.h +++ b/src/filebuf.h @@ -19,7 +19,8 @@ #define GIT_FILEBUF_APPEND (1 << 2) #define GIT_FILEBUF_FORCE (1 << 3) #define GIT_FILEBUF_TEMPORARY (1 << 4) -#define GIT_FILEBUF_DEFLATE_SHIFT (5) +#define GIT_FILEBUF_DO_NOT_BUFFER (1 << 5) +#define GIT_FILEBUF_DEFLATE_SHIFT (6) #define GIT_FILELOCK_EXTENSION ".lock\0" #define GIT_FILELOCK_EXTLENGTH 6 @@ -40,25 +41,37 @@ struct git_filebuf { size_t buf_size, buf_pos; git_file fd; + bool fd_is_open; + bool do_not_buffer; + int last_error; }; typedef struct git_filebuf git_filebuf; #define GIT_FILEBUF_INIT {0} -/* The git_filebuf object lifecycle is: +/* + * The git_filebuf object lifecycle is: * - Allocate git_filebuf, preferably using GIT_FILEBUF_INIT. + * * - Call git_filebuf_open() to initialize the filebuf for use. + * * - Make as many calls to git_filebuf_write(), git_filebuf_printf(), - * git_filebuf_reserve() as you like. + * git_filebuf_reserve() as you like. The error codes for these + * functions don't need to be checked. They are stored internally + * by the file buffer. + * * - While you are writing, you may call git_filebuf_hash() to get - * the hash of all you have written so far. + * the hash of all you have written so far. This function will + * fail if any of the previous writes to the buffer failed. + * * - To close the git_filebuf, you may call git_filebuf_commit() or * git_filebuf_commit_at() to save the file, or * git_filebuf_cleanup() to abandon the file. All of these will - * clear the git_filebuf object. + * free the git_filebuf object. Likewise, all of these will fail + * if any of the previous writes to the buffer failed, and set + * an error code accordingly. */ - int git_filebuf_write(git_filebuf *lock, const void *buff, size_t len); int git_filebuf_reserve(git_filebuf *file, void **buff, size_t len); int git_filebuf_printf(git_filebuf *file, const char *format, ...) GIT_FORMAT_PRINTF(2, 3); diff --git a/src/fileops.c b/src/fileops.c index 856823afb..bf95f769c 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -10,25 +10,19 @@ int git_futils_mkpath2file(const char *file_path, const mode_t mode) { - int error; + int result = 0; git_buf target_folder = GIT_BUF_INIT; - error = git_path_dirname_r(&target_folder, file_path); - if (error < GIT_SUCCESS) { - git_buf_free(&target_folder); - return git__throw(GIT_EINVALIDPATH, "Failed to recursively build `%s` tree structure. Unable to parse parent folder name", file_path); - } else { - /* reset error */ - error = GIT_SUCCESS; - } + if (git_path_dirname_r(&target_folder, file_path) < 0) + return -1; /* Does the containing folder exist? */ - if (git_path_isdir(target_folder.ptr) != GIT_SUCCESS) + if (git_path_isdir(target_folder.ptr) == false) /* Let's create the tree structure */ - error = git_futils_mkdir_r(target_folder.ptr, NULL, mode); + result = git_futils_mkdir_r(target_folder.ptr, NULL, mode); git_buf_free(&target_folder); - return error; + return result; } int git_futils_mktmp(git_buf *path_out, const char *filename) @@ -39,42 +33,82 @@ int git_futils_mktmp(git_buf *path_out, const char *filename) git_buf_puts(path_out, "_git2_XXXXXX"); if (git_buf_oom(path_out)) - return git__rethrow(git_buf_lasterror(path_out), - "Failed to create temporary file for %s", filename); + return -1; - if ((fd = p_mkstemp(path_out->ptr)) < 0) - return git__throw(GIT_EOSERR, "Failed to create temporary file %s", path_out->ptr); + if ((fd = p_mkstemp(path_out->ptr)) < 0) { + giterr_set(GITERR_OS, + "Failed to create temporary file '%s'", path_out->ptr); + return -1; + } return fd; } int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode) { - if (git_futils_mkpath2file(path, dirmode) < GIT_SUCCESS) - return git__throw(GIT_EOSERR, "Failed to create file %s", path); + int fd; + + if (git_futils_mkpath2file(path, dirmode) < 0) + return -1; - return p_creat(path, mode); + fd = p_creat(path, mode); + if (fd < 0) { + giterr_set(GITERR_OS, "Failed to create file '%s'", path); + return -1; + } + + return fd; } int git_futils_creat_locked(const char *path, const mode_t mode) { - int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode); - return fd >= 0 ? fd : git__throw(GIT_EOSERR, "Failed to create locked file. Could not open %s", path); + int fd; + +#ifdef GIT_WIN32 + wchar_t* buf; + + buf = gitwin_to_utf16(path); + fd = _wopen(buf, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode); + git__free(buf); +#else + fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode); +#endif + + if (fd < 0) { + giterr_set(GITERR_OS, "Failed to create locked file '%s'", path); + return -1; + } + + return fd; } int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode) { - if (git_futils_mkpath2file(path, dirmode) < GIT_SUCCESS) - return git__throw(GIT_EOSERR, "Failed to create locked file %s", path); + if (git_futils_mkpath2file(path, dirmode) < 0) + return -1; return git_futils_creat_locked(path, mode); } +int git_futils_open_ro(const char *path) +{ + int fd = p_open(path, O_RDONLY); + if (fd < 0) { + if (errno == ENOENT) + fd = GIT_ENOTFOUND; + giterr_set(GITERR_OS, "Failed to open '%s'", path); + } + return fd; +} + git_off_t git_futils_filesize(git_file fd) { struct stat sb; - if (p_fstat(fd, &sb)) - return GIT_ERROR; + + if (p_fstat(fd, &sb)) { + giterr_set(GITERR_OS, "Failed to stat file descriptor"); + return -1; + } return sb.st_size; } @@ -85,10 +119,10 @@ mode_t git_futils_canonical_mode(mode_t raw_mode) return S_IFREG | GIT_CANONICAL_PERMS(raw_mode); else if (S_ISLNK(raw_mode)) return S_IFLNK; - else if (S_ISDIR(raw_mode)) - return S_IFDIR; else if (S_ISGITLINK(raw_mode)) return S_IFGITLINK; + else if (S_ISDIR(raw_mode)) + return S_IFDIR; else return 0; } @@ -104,13 +138,13 @@ int git_futils_readbuffer_updated(git_buf *buf, const char *path, time_t *mtime, if (updated != NULL) *updated = 0; - if ((fd = p_open(path, O_RDONLY)) < 0) { - return git__throw(GIT_ENOTFOUND, "Failed to read file '%s': %s", path, strerror(errno)); - } + if ((fd = git_futils_open_ro(path)) < 0) + return fd; if (p_fstat(fd, &st) < 0 || S_ISDIR(st.st_mode) || !git__is_sizet(st.st_size+1)) { - close(fd); - return git__throw(GIT_EOSERR, "Failed to stat file '%s'", path); + p_close(fd); + giterr_set(GITERR_OS, "Invalid regular file stat for '%s'", path); + return -1; } /* @@ -118,7 +152,7 @@ int git_futils_readbuffer_updated(git_buf *buf, const char *path, time_t *mtime, * has been modified. */ if (mtime != NULL && *mtime >= st.st_mtime) { - close(fd); + p_close(fd); return 0; } @@ -130,8 +164,8 @@ int git_futils_readbuffer_updated(git_buf *buf, const char *path, time_t *mtime, git_buf_clear(buf); if (git_buf_grow(buf, len + 1) < 0) { - close(fd); - return GIT_ENOMEM; + p_close(fd); + return -1; } buf->ptr[len] = '\0'; @@ -140,8 +174,9 @@ int git_futils_readbuffer_updated(git_buf *buf, const char *path, time_t *mtime, ssize_t read_size = p_read(fd, buf->ptr, len); if (read_size < 0) { - close(fd); - return git__throw(GIT_EOSERR, "Failed to read from FD"); + p_close(fd); + giterr_set(GITERR_OS, "Failed to read descriptor for '%s'", path); + return -1; } len -= read_size; @@ -166,10 +201,15 @@ int git_futils_readbuffer(git_buf *buf, const char *path) int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode) { - if (git_futils_mkpath2file(to, dirmode) < GIT_SUCCESS) - return GIT_EOSERR; /* The callee already takes care of setting the correct error message. */ + if (git_futils_mkpath2file(to, dirmode) < 0) + return -1; + + if (p_rename(from, to) < 0) { + giterr_set(GITERR_OS, "Failed to rename '%s' to '%s'", from, to); + return -1; + } - return p_rename(from, to); /* The callee already takes care of setting the correct error message. */ + return 0; } int git_futils_mmap_ro(git_map *out, git_file fd, git_off_t begin, size_t len) @@ -179,11 +219,19 @@ int git_futils_mmap_ro(git_map *out, git_file fd, git_off_t begin, size_t len) int git_futils_mmap_ro_file(git_map *out, const char *path) { - git_file fd = p_open(path, O_RDONLY /* | O_NOATIME */); - git_off_t len = git_futils_filesize(fd); + git_file fd = git_futils_open_ro(path); + git_off_t len; int result; - if (!git__is_sizet(len)) - return git__throw(GIT_ERROR, "File `%s` too large to mmap", path); + + if (fd < 0) + return fd; + + len = git_futils_filesize(fd); + if (!git__is_sizet(len)) { + giterr_set(GITERR_OS, "File `%s` too large to mmap", path); + return -1; + } + result = git_futils_mmap_ro(out, fd, 0, (size_t)len); p_close(fd); return result; @@ -196,20 +244,21 @@ void git_futils_mmap_free(git_map *out) int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode) { - int error, root_path_offset; + int root_path_offset; git_buf make_path = GIT_BUF_INIT; size_t start; char *pp, *sp; + bool failed = false; if (base != NULL) { start = strlen(base); - error = git_buf_joinpath(&make_path, base, path); + if (git_buf_joinpath(&make_path, base, path) < 0) + return -1; } else { start = 0; - error = git_buf_puts(&make_path, path); + if (git_buf_puts(&make_path, path) < 0) + return -1; } - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to create `%s` tree structure", path); pp = make_path.ptr + start; @@ -217,14 +266,13 @@ int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode) if (root_path_offset > 0) pp += root_path_offset; /* On Windows, will skip the drive name (eg. C: or D:) */ - while (error == GIT_SUCCESS && (sp = strchr(pp, '/')) != NULL) { - if (sp != pp && git_path_isdir(make_path.ptr) < GIT_SUCCESS) { + while (!failed && (sp = strchr(pp, '/')) != NULL) { + if (sp != pp && git_path_isdir(make_path.ptr) == false) { *sp = 0; - error = p_mkdir(make_path.ptr, mode); /* Do not choke while trying to recreate an existing directory */ - if (errno == EEXIST) - error = GIT_SUCCESS; + if (p_mkdir(make_path.ptr, mode) < 0 && errno != EEXIST) + failed = true; *sp = '/'; } @@ -232,53 +280,76 @@ int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode) pp = sp + 1; } - if (*pp != '\0' && error == GIT_SUCCESS) { - error = p_mkdir(make_path.ptr, mode); - if (errno == EEXIST) - error = GIT_SUCCESS; + if (*pp != '\0' && !failed) { + if (p_mkdir(make_path.ptr, mode) < 0 && errno != EEXIST) + failed = true; } git_buf_free(&make_path); - if (error < GIT_SUCCESS) - return git__throw(error, "Failed to recursively create `%s` tree structure", path); + if (failed) { + giterr_set(GITERR_OS, + "Failed to create directory structure at '%s'", path); + return -1; + } - return GIT_SUCCESS; + return 0; } static int _rmdir_recurs_foreach(void *opaque, git_buf *path) { - int error = GIT_SUCCESS; - int force = *(int *)opaque; + git_directory_removal_type removal_type = *(git_directory_removal_type *)opaque; + + assert(removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY + || removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS + || removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS); - if (git_path_isdir(path->ptr) == GIT_SUCCESS) { - error = git_path_direach(path, _rmdir_recurs_foreach, opaque); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to remove directory `%s`", path->ptr); - return p_rmdir(path->ptr); + if (git_path_isdir(path->ptr) == true) { + if (git_path_direach(path, _rmdir_recurs_foreach, opaque) < 0) + return -1; - } else if (force) { - return p_unlink(path->ptr); + if (p_rmdir(path->ptr) < 0) { + if (removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS && errno == ENOTEMPTY) + return 0; + + giterr_set(GITERR_OS, "Could not remove directory '%s'", path->ptr); + return -1; + } + + return 0; } - return git__rethrow(error, "Failed to remove directory. `%s` is not empty", path->ptr); + if (removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS) { + if (p_unlink(path->ptr) < 0) { + giterr_set(GITERR_OS, "Could not remove directory. File '%s' cannot be removed", path->ptr); + return -1; + } + + return 0; + } + + if (removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY) { + giterr_set(GITERR_OS, "Could not remove directory. File '%s' still present", path->ptr); + return -1; + } + + return 0; } -int git_futils_rmdir_r(const char *path, int force) +int git_futils_rmdir_r(const char *path, git_directory_removal_type removal_type) { int error; git_buf p = GIT_BUF_INIT; error = git_buf_sets(&p, path); - if (error == GIT_SUCCESS) - error = _rmdir_recurs_foreach(&force, &p); + if (!error) + error = _rmdir_recurs_foreach(&removal_type, &p); git_buf_free(&p); return error; } int git_futils_find_global_file(git_buf *path, const char *filename) { - int error; const char *home = getenv("HOME"); #ifdef GIT_WIN32 @@ -286,19 +357,21 @@ int git_futils_find_global_file(git_buf *path, const char *filename) home = getenv("USERPROFILE"); #endif - if (home == NULL) - return git__throw(GIT_EOSERR, "Failed to open global %s file. " - "Cannot locate the user's home directory.", filename); + if (home == NULL) { + giterr_set(GITERR_OS, "Global file lookup failed. " + "Cannot locate the user's home directory"); + return -1; + } - if ((error = git_buf_joinpath(path, home, filename)) < GIT_SUCCESS) - return error; + if (git_buf_joinpath(path, home, filename) < 0) + return -1; - if (git_path_exists(path->ptr) < GIT_SUCCESS) { + if (git_path_exists(path->ptr) == false) { git_buf_clear(path); return GIT_ENOTFOUND; } - return GIT_SUCCESS; + return 0; } #ifdef GIT_WIN32 @@ -315,9 +388,8 @@ static const win32_path *win32_system_root(void) const wchar_t *root_tmpl = L"%PROGRAMFILES%\\Git\\etc\\"; s_root.len = ExpandEnvironmentStringsW(root_tmpl, NULL, 0); - if (s_root.len <= 0) { - git__throw(GIT_EOSERR, "Failed to expand environment strings"); + giterr_set(GITERR_OS, "Failed to expand environment strings"); return NULL; } @@ -326,7 +398,7 @@ static const win32_path *win32_system_root(void) return NULL; if (ExpandEnvironmentStringsW(root_tmpl, s_root.path, s_root.len) != s_root.len) { - git__throw(GIT_EOSERR, "Failed to expand environment strings"); + giterr_set(GITERR_OS, "Failed to expand environment strings"); git__free(s_root.path); s_root.path = NULL; return NULL; @@ -338,7 +410,7 @@ static const win32_path *win32_system_root(void) static int win32_find_system_file(git_buf *path, const char *filename) { - int error = GIT_SUCCESS; + int error = 0; const win32_path *root = win32_system_root(); size_t len; wchar_t *file_utf16 = NULL, *scan; @@ -349,8 +421,7 @@ static int win32_find_system_file(git_buf *path, const char *filename) /* allocate space for wchar_t path to file */ file_utf16 = git__calloc(root->len + len + 2, sizeof(wchar_t)); - if (!file_utf16) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(file_utf16); /* append root + '\\' + filename as wchar_t */ memcpy(file_utf16, root->path, root->len * sizeof(wchar_t)); @@ -360,7 +431,7 @@ static int win32_find_system_file(git_buf *path, const char *filename) if (gitwin_append_utf16(file_utf16 + root->len - 1, filename, len + 1) != (int)len + 1) { - error = git__throw(GIT_EOSERR, "Failed to build file path"); + error = -1; goto cleanup; } @@ -376,9 +447,8 @@ static int win32_find_system_file(git_buf *path, const char *filename) /* convert to utf8 */ if ((file_utf8 = gitwin_from_utf16(file_utf16)) == NULL) - error = GIT_ENOMEM; - - if (file_utf8) { + error = -1; + else { git_path_mkposix(file_utf8); git_buf_attach(path, file_utf8, 0); } @@ -392,11 +462,11 @@ cleanup: int git_futils_find_system_file(git_buf *path, const char *filename) { - if (git_buf_joinpath(path, "/etc", filename) < GIT_SUCCESS) - return git_buf_lasterror(path); + if (git_buf_joinpath(path, "/etc", filename) < 0) + return -1; - if (git_path_exists(path->ptr) == GIT_SUCCESS) - return GIT_SUCCESS; + if (git_path_exists(path->ptr) == true) + return 0; git_buf_clear(path); diff --git a/src/fileops.h b/src/fileops.h index ab57b6f38..be619d620 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -26,7 +26,7 @@ extern int git_futils_readbuffer_updated(git_buf *obj, const char *path, time_t * These are custom filesystem-related helper methods. They are * rather high level, and wrap the underlying POSIX methods * - * All these methods return GIT_SUCCESS on success, + * All these methods return 0 on success, * or an error code on failure and an error message is set. */ @@ -58,10 +58,25 @@ extern int git_futils_mkdir_r(const char *path, const char *base, const mode_t m */ extern int git_futils_mkpath2file(const char *path, const mode_t mode); +typedef enum { + GIT_DIRREMOVAL_EMPTY_HIERARCHY = 0, + GIT_DIRREMOVAL_FILES_AND_DIRS = 1, + GIT_DIRREMOVAL_ONLY_EMPTY_DIRS = 2, +} git_directory_removal_type; + /** * Remove path and any files and directories beneath it. + * + * @param path Path to to top level directory to process. + * + * @param removal_type GIT_DIRREMOVAL_EMPTY_HIERARCHY to remove a hierarchy + * of empty directories (will fail if any file is found), GIT_DIRREMOVAL_FILES_AND_DIRS + * to remove a hierarchy of files and folders, GIT_DIRREMOVAL_ONLY_EMPTY_DIRS to only remove + * empty directories (no failure on file encounter). + * + * @return 0 on success; -1 on error. */ -extern int git_futils_rmdir_r(const char *path, int force); +extern int git_futils_rmdir_r(const char *path, git_directory_removal_type removal_type); /** * Create and open a temporary file with a `_git2_` suffix. @@ -77,6 +92,11 @@ extern int git_futils_mktmp(git_buf *path_out, const char *filename); extern int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode); /** + * Open a file readonly and set error if needed. + */ +extern int git_futils_open_ro(const char *path); + +/** * Get the filesize in bytes of a file */ extern git_off_t git_futils_filesize(git_file fd); @@ -103,8 +123,8 @@ extern mode_t git_futils_canonical_mode(mode_t raw_mode); * @param begin first byte to map, this should be page aligned. * @param end number of bytes to map. * @return - * - GIT_SUCCESS on success; - * - GIT_EOSERR on an unspecified OS related error. + * - 0 on success; + * - -1 on error. */ extern int git_futils_mmap_ro( git_map *out, @@ -118,8 +138,9 @@ extern int git_futils_mmap_ro( * @param out buffer to populate with the mapping information. * @param path path to file to be opened. * @return - * - GIT_SUCCESS on success; - * - GIT_EOSERR on an unspecified OS related error. + * - 0 on success; + * - GIT_ENOTFOUND if not found; + * - -1 on an unspecified OS related error. */ extern int git_futils_mmap_ro_file( git_map *out, @@ -137,9 +158,9 @@ extern void git_futils_mmap_free(git_map *map); * @param pathbuf buffer to write the full path into * @param filename name of file to find in the home directory * @return - * - GIT_SUCCESS if found; + * - 0 if found; * - GIT_ENOTFOUND if not found; - * - GIT_EOSERR on an unspecified OS related error. + * - -1 on an unspecified OS related error. */ extern int git_futils_find_global_file(git_buf *path, const char *filename); @@ -149,9 +170,9 @@ extern int git_futils_find_global_file(git_buf *path, const char *filename); * @param pathbuf buffer to write the full path into * @param filename name of file to find in the home directory * @return - * - GIT_SUCCESS if found; + * - 0 if found; * - GIT_ENOTFOUND if not found; - * - GIT_EOSERR on an unspecified OS related error. + * - -1 on an unspecified OS related error. */ extern int git_futils_find_system_file(git_buf *path, const char *filename); diff --git a/src/filter.c b/src/filter.c index f0ee1ad39..d6c2e1c97 100644 --- a/src/filter.c +++ b/src/filter.c @@ -19,13 +19,13 @@ void git_text_gather_stats(git_text_stats *stats, const git_buf *text) memset(stats, 0, sizeof(*stats)); - for (i = 0; i < text->size; i++) { + for (i = 0; i < git_buf_len(text); i++) { unsigned char c = text->ptr[i]; if (c == '\r') { stats->cr++; - if (i + 1 < text->size && text->ptr[i + 1] == '\n') + if (i + 1 < git_buf_len(text) && text->ptr[i + 1] == '\n') stats->crlf++; } @@ -59,7 +59,7 @@ void git_text_gather_stats(git_text_stats *stats, const git_buf *text) } /* If file ends with EOF then don't count this EOF as non-printable. */ - if (text->size >= 1 && text->ptr[text->size - 1] == '\032') + if (git_buf_len(text) >= 1 && text->ptr[text->size - 1] == '\032') stats->nonprintable--; } @@ -95,8 +95,8 @@ int git_filters_load(git_vector *filters, git_repository *repo, const char *path if (error < GIT_SUCCESS) return error; } else { - return git__throw(GIT_ENOTIMPLEMENTED, - "Worktree filters are not implemented yet"); + giterr_set(GITERR_INVALID, "Worktree filters are not implemented yet"); + return GIT_ENOTIMPLEMENTED; } return (int)filters->length; @@ -111,7 +111,7 @@ void git_filters_free(git_vector *filters) if (filter->do_free != NULL) filter->do_free(filter); else - free(filter); + git__free(filter); } git_vector_free(filters); @@ -127,14 +127,14 @@ int git_filters_apply(git_buf *dest, git_buf *source, git_vector *filters) src = 0; - if (source->size == 0) { + if (git_buf_len(source) == 0) { git_buf_clear(dest); return GIT_SUCCESS; } /* Pre-grow the destination buffer to more or less the size * we expect it to have */ - if (git_buf_grow(dest, source->size) < 0) + if (git_buf_grow(dest, git_buf_len(source)) < 0) return GIT_ENOMEM; for (i = 0; i < filters->length; ++i) { diff --git a/src/global.c b/src/global.c index b10fabc61..368c6c664 100644 --- a/src/global.c +++ b/src/global.c @@ -62,7 +62,7 @@ git_global_st *git__global_state(void) if ((ptr = TlsGetValue(_tls_index)) != NULL) return ptr; - ptr = malloc(sizeof(git_global_st)); + ptr = git__malloc(sizeof(git_global_st)); if (!ptr) return NULL; @@ -78,7 +78,7 @@ static int _tls_init = 0; static void cb__free_status(void *st) { - free(st); + git__free(st); } void git_threads_init(void) @@ -103,7 +103,7 @@ git_global_st *git__global_state(void) if ((ptr = pthread_getspecific(_tls_key)) != NULL) return ptr; - ptr = malloc(sizeof(git_global_st)); + ptr = git__malloc(sizeof(git_global_st)); if (!ptr) return NULL; diff --git a/src/global.h b/src/global.h index 0c1e3289c..2b525ce07 100644 --- a/src/global.h +++ b/src/global.h @@ -14,6 +14,9 @@ typedef struct { char last[1024]; } error; + git_error *last_error; + git_error error_t; + git_mwindow_ctl mem_ctl; } git_global_st; diff --git a/src/hashtable.c b/src/hashtable.c deleted file mode 100644 index 73a6336c4..000000000 --- a/src/hashtable.c +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (C) 2009-2012 the libgit2 contributors - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "common.h" -#include "repository.h" -#include "commit.h" - -#define MAX_LOOPS 5 -static const double max_load_factor = 0.65; - -static int resize_to(git_hashtable *self, size_t new_size); -static int set_size(git_hashtable *self, size_t new_size); -static git_hashtable_node *node_with_hash(git_hashtable *self, const void *key, int hash_id); -static void node_swap_with(git_hashtable_node *self, git_hashtable_node *other); -static int node_insert(git_hashtable *self, git_hashtable_node *new_node); -static int insert_nodes(git_hashtable *self, git_hashtable_node *old_nodes, size_t old_size); - -static int resize_to(git_hashtable *self, size_t new_size) -{ - git_hashtable_node *old_nodes = self->nodes; - size_t old_size = self->size; - - self->is_resizing = 1; - - do { - self->size = new_size; - self->size_mask = new_size - 1; - self->key_count = 0; - self->nodes = git__calloc(1, sizeof(git_hashtable_node) * self->size); - - if (self->nodes == NULL) - return GIT_ENOMEM; - - if (insert_nodes(self, old_nodes, old_size) == 0) - self->is_resizing = 0; - else { - new_size *= 2; - git__free(self->nodes); - } - } while(self->is_resizing); - - git__free(old_nodes); - return GIT_SUCCESS; -} - -static int set_size(git_hashtable *self, size_t new_size) -{ - self->nodes = git__realloc(self->nodes, new_size * sizeof(git_hashtable_node)); - if (self->nodes == NULL) - return GIT_ENOMEM; - - if (new_size > self->size) { - memset(&self->nodes[self->size], 0x0, (new_size - self->size) * sizeof(git_hashtable_node)); - } - - self->size = new_size; - self->size_mask = new_size - 1; - return GIT_SUCCESS; -} - -static git_hashtable_node *node_with_hash(git_hashtable *self, const void *key, int hash_id) -{ - size_t pos = self->hash(key, hash_id) & self->size_mask; - return git_hashtable_node_at(self->nodes, pos); -} - -static void node_swap_with(git_hashtable_node *self, git_hashtable_node *other) -{ - git_hashtable_node tmp = *self; - *self = *other; - *other = tmp; -} - -static int node_insert(git_hashtable *self, git_hashtable_node *new_node) -{ - int iteration, hash_id; - - for (iteration = 0; iteration < MAX_LOOPS; iteration++) { - for (hash_id = 0; hash_id < GIT_HASHTABLE_HASHES; ++hash_id) { - git_hashtable_node *node; - node = node_with_hash(self, new_node->key, hash_id); - node_swap_with(new_node, node); - if(new_node->key == 0x0){ - self->key_count++; - return GIT_SUCCESS; - } - } - } - - if (self->is_resizing) - return git__throw(GIT_EBUSY, "Failed to insert node. Hashtable is currently resizing"); - - resize_to(self, self->size * 2); - git_hashtable_insert(self, new_node->key, new_node->value); - return GIT_SUCCESS; -} - -static int insert_nodes(git_hashtable *self, git_hashtable_node *old_nodes, size_t old_size) -{ - size_t i; - - for (i = 0; i < old_size; ++i) { - git_hashtable_node *node = git_hashtable_node_at(old_nodes, i); - if (node->key && git_hashtable_insert(self, node->key, node->value) < GIT_SUCCESS) - return GIT_ENOMEM; - } - - return GIT_SUCCESS; -} - -git_hashtable *git_hashtable_alloc(size_t min_size, - git_hash_ptr hash, - git_hash_keyeq_ptr key_eq) -{ - git_hashtable *table; - - assert(hash && key_eq); - - if ((table = git__malloc(sizeof(git_hashtable))) == NULL) - return NULL; - - memset(table, 0x0, sizeof(git_hashtable)); - - if (min_size < 8) - min_size = 8; - - /* round up size to closest power of 2 */ - min_size--; - min_size |= min_size >> 1; - min_size |= min_size >> 2; - min_size |= min_size >> 4; - min_size |= min_size >> 8; - min_size |= min_size >> 16; - - table->hash = hash; - table->key_equal = key_eq; - - set_size(table, min_size + 1); - - return table; -} - -void git_hashtable_clear(git_hashtable *self) -{ - assert(self); - - memset(self->nodes, 0x0, sizeof(git_hashtable_node) * self->size); - self->key_count = 0; -} - -void git_hashtable_free(git_hashtable *self) -{ - assert(self); - - git__free(self->nodes); - git__free(self); -} - - -int git_hashtable_insert2(git_hashtable *self, const void *key, void *value, void **old_value) -{ - int hash_id; - git_hashtable_node *node; - - assert(self && self->nodes); - - *old_value = NULL; - - for (hash_id = 0; hash_id < GIT_HASHTABLE_HASHES; ++hash_id) { - node = node_with_hash(self, key, hash_id); - - if (!node->key) { - node->key = key; - node->value = value; - self->key_count++; - return GIT_SUCCESS; - } - - if (key == node->key || self->key_equal(key, node->key) == 0) { - *old_value = node->value; - node->key = key; - node->value = value; - return GIT_SUCCESS; - } - } - - /* no space in table; must do cuckoo dance */ - { - git_hashtable_node x; - x.key = key; - x.value = value; - return node_insert(self, &x); - } -} - -void *git_hashtable_lookup(git_hashtable *self, const void *key) -{ - int hash_id; - git_hashtable_node *node; - - assert(self && self->nodes); - - for (hash_id = 0; hash_id < GIT_HASHTABLE_HASHES; ++hash_id) { - node = node_with_hash(self, key, hash_id); - if (node->key && self->key_equal(key, node->key) == 0) - return node->value; - } - - return NULL; -} - -int git_hashtable_remove2(git_hashtable *self, const void *key, void **old_value) -{ - int hash_id; - git_hashtable_node *node; - - assert(self && self->nodes); - - for (hash_id = 0; hash_id < GIT_HASHTABLE_HASHES; ++hash_id) { - node = node_with_hash(self, key, hash_id); - if (node->key && self->key_equal(key, node->key) == 0) { - *old_value = node->value; - node->key = NULL; - node->value = NULL; - self->key_count--; - return GIT_SUCCESS; - } - } - - return git__throw(GIT_ENOTFOUND, "Entry not found in hash table"); -} - -int git_hashtable_merge(git_hashtable *self, git_hashtable *other) -{ - if (resize_to(self, (self->size + other->size) * 2) < GIT_SUCCESS) - return GIT_ENOMEM; - - return insert_nodes(self, other->nodes, other->key_count); -} - - -/** - * Standard string - */ -uint32_t git_hash__strhash_cb(const void *key, int hash_id) -{ - static uint32_t hash_seeds[GIT_HASHTABLE_HASHES] = { - 2147483647, - 0x5d20bb23, - 0x7daaab3c - }; - - return git__hash(key, strlen((const char *)key), hash_seeds[hash_id]); -} diff --git a/src/hashtable.h b/src/hashtable.h deleted file mode 100644 index e09965965..000000000 --- a/src/hashtable.h +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2009-2012 the libgit2 contributors - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_hashtable_h__ -#define INCLUDE_hashtable_h__ - -#include "git2/common.h" -#include "git2/oid.h" -#include "git2/odb.h" -#include "common.h" - -#define GIT_HASHTABLE_HASHES 3 - -typedef uint32_t (*git_hash_ptr)(const void *, int hash_id); -typedef int (*git_hash_keyeq_ptr)(const void *key_a, const void *key_b); - -struct git_hashtable_node { - const void *key; - void *value; -}; - -struct git_hashtable { - struct git_hashtable_node *nodes; - - size_t size_mask; - size_t size; - size_t key_count; - - int is_resizing; - - git_hash_ptr hash; - git_hash_keyeq_ptr key_equal; -}; - -typedef struct git_hashtable_node git_hashtable_node; -typedef struct git_hashtable git_hashtable; - -git_hashtable *git_hashtable_alloc(size_t min_size, - git_hash_ptr hash, - git_hash_keyeq_ptr key_eq); -void *git_hashtable_lookup(git_hashtable *h, const void *key); -int git_hashtable_remove2(git_hashtable *table, const void *key, void **old_value); - -GIT_INLINE(int) git_hashtable_remove(git_hashtable *table, const void *key) -{ - void *_unused; - return git_hashtable_remove2(table, key, &_unused); -} - - -void git_hashtable_free(git_hashtable *h); -void git_hashtable_clear(git_hashtable *h); -int git_hashtable_merge(git_hashtable *self, git_hashtable *other); - -int git_hashtable_insert2(git_hashtable *h, const void *key, void *value, void **old_value); - -GIT_INLINE(int) git_hashtable_insert(git_hashtable *h, const void *key, void *value) -{ - void *_unused; - return git_hashtable_insert2(h, key, value, &_unused); -} - -#define git_hashtable_node_at(nodes, pos) ((git_hashtable_node *)(&nodes[pos])) - -#define GIT_HASHTABLE__FOREACH(self,block) { \ - unsigned int _c; \ - git_hashtable_node *_n = (self)->nodes; \ - for (_c = (self)->size; _c > 0; _c--, _n++) { \ - if (!_n->key) continue; block } } - -#define GIT_HASHTABLE_FOREACH(self, pkey, pvalue, code)\ - GIT_HASHTABLE__FOREACH(self,{(pkey)=_n->key;(pvalue)=_n->value;code;}) - -#define GIT_HASHTABLE_FOREACH_KEY(self, pkey, code)\ - GIT_HASHTABLE__FOREACH(self,{(pkey)=_n->key;code;}) - -#define GIT_HASHTABLE_FOREACH_VALUE(self, pvalue, code)\ - GIT_HASHTABLE__FOREACH(self,{(pvalue)=_n->value;code;}) - -#define GIT_HASHTABLE_FOREACH_DELETE() {\ - _node->key = NULL; _node->value = NULL; _self->key_count--;\ -} - -/* - * If you want a hashtable with standard string keys, you can - * just pass git_hash__strcmp_cb and git_hash__strhash_cb to - * git_hashtable_alloc. - */ -#define git_hash__strcmp_cb git__strcmp_cb -extern uint32_t git_hash__strhash_cb(const void *key, int hash_id); - -#endif diff --git a/src/ignore.c b/src/ignore.c index a3bf0a282..20b96c602 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -1,53 +1,54 @@ #include "ignore.h" #include "path.h" -#include "git2/config.h" #define GIT_IGNORE_INTERNAL "[internal]exclude" #define GIT_IGNORE_FILE_INREPO "info/exclude" #define GIT_IGNORE_FILE ".gitignore" -#define GIT_IGNORE_CONFIG "core.excludesfile" static int load_ignore_file( git_repository *repo, const char *path, git_attr_file *ignores) { - int error = GIT_SUCCESS; + int error; git_buf fbuf = GIT_BUF_INIT; git_attr_fnmatch *match = NULL; const char *scan = NULL; char *context = NULL; - if (ignores->path == NULL) - error = git_attr_file__set_path(repo, path, ignores); + if (ignores->path == NULL) { + if (git_attr_file__set_path(repo, path, ignores) < 0) + return -1; + } if (git__suffixcmp(ignores->path, GIT_IGNORE_FILE) == 0) { context = git__strndup(ignores->path, strlen(ignores->path) - strlen(GIT_IGNORE_FILE)); - if (!context) error = GIT_ENOMEM; + GITERR_CHECK_ALLOC(context); } - if (error == GIT_SUCCESS) - error = git_futils_readbuffer(&fbuf, path); + error = git_futils_readbuffer(&fbuf, path); scan = fbuf.ptr; - while (error == GIT_SUCCESS && *scan) { - if (!match && !(match = git__calloc(1, sizeof(git_attr_fnmatch)))) { - error = GIT_ENOMEM; - break; + while (!error && *scan) { + if (!match) { + match = git__calloc(1, sizeof(*match)); + GITERR_CHECK_ALLOC(match); } - if (!(error = git_attr_fnmatch__parse(match, context, &scan))) { + if (!(error = git_attr_fnmatch__parse( + match, ignores->pool, context, &scan))) + { match->flags = match->flags | GIT_ATTR_FNMATCH_IGNORE; scan = git__next_line(scan); error = git_vector_insert(&ignores->rules, match); } - if (error != GIT_SUCCESS) { + if (error != 0) { git__free(match->pattern); match->pattern = NULL; if (error == GIT_ENOTFOUND) - error = GIT_SUCCESS; + error = 0; } else { match = NULL; /* vector now "owns" the match */ } @@ -57,9 +58,6 @@ static int load_ignore_file( git__free(match); git__free(context); - if (error != GIT_SUCCESS) - git__rethrow(error, "Could not open ignore file '%s'", path); - return error; } @@ -74,8 +72,7 @@ static int push_one_ignore(void *ref, git_buf *path) int git_ignore__for_path(git_repository *repo, const char *path, git_ignores *ignores) { - int error = GIT_SUCCESS; - git_config *cfg; + int error = 0; const char *workdir = git_repository_workdir(repo); assert(ignores); @@ -83,63 +80,52 @@ int git_ignore__for_path(git_repository *repo, const char *path, git_ignores *ig ignores->repo = repo; git_buf_init(&ignores->dir, 0); ignores->ign_internal = NULL; - git_vector_init(&ignores->ign_path, 8, NULL); - git_vector_init(&ignores->ign_global, 2, NULL); - if ((error = git_attr_cache__init(repo)) < GIT_SUCCESS) + if ((error = git_vector_init(&ignores->ign_path, 8, NULL)) < 0 || + (error = git_vector_init(&ignores->ign_global, 2, NULL)) < 0 || + (error = git_attr_cache__init(repo)) < 0) goto cleanup; - if ((error = git_path_find_dir(&ignores->dir, path, workdir)) < GIT_SUCCESS) + /* translate path into directory within workdir */ + if ((error = git_path_find_dir(&ignores->dir, path, workdir)) < 0) goto cleanup; /* set up internals */ error = git_attr_cache__lookup_or_create_file( repo, GIT_IGNORE_INTERNAL, NULL, NULL, &ignores->ign_internal); - if (error < GIT_SUCCESS) + if (error < 0) goto cleanup; /* load .gitignore up the path */ error = git_path_walk_up(&ignores->dir, workdir, push_one_ignore, ignores); - if (error < GIT_SUCCESS) + if (error < 0) goto cleanup; /* load .git/info/exclude */ error = push_ignore(repo, &ignores->ign_global, - repo->path_repository, GIT_IGNORE_FILE_INREPO); - if (error < GIT_SUCCESS) + git_repository_path(repo), GIT_IGNORE_FILE_INREPO); + if (error < 0) goto cleanup; /* load core.excludesfile */ - if ((error = git_repository_config(&cfg, repo)) == GIT_SUCCESS) { - const char *core_ignore; - error = git_config_get_string(cfg, GIT_IGNORE_CONFIG, &core_ignore); - if (error == GIT_SUCCESS && core_ignore != NULL) - error = push_ignore(repo, &ignores->ign_global, NULL, core_ignore); - else { - error = GIT_SUCCESS; - git_clearerror(); /* don't care if attributesfile is not set */ - } - git_config_free(cfg); - } + if (git_repository_attr_cache(repo)->cfg_excl_file != NULL) + error = push_ignore(repo, &ignores->ign_global, NULL, + git_repository_attr_cache(repo)->cfg_excl_file); cleanup: - if (error < GIT_SUCCESS) { + if (error < 0) git_ignore__free(ignores); - git__rethrow(error, "Could not get ignore files for '%s'", path); - } return error; } int git_ignore__push_dir(git_ignores *ign, const char *dir) { - int error = git_buf_joinpath(&ign->dir, ign->dir.ptr, dir); - - if (error == GIT_SUCCESS) - error = push_ignore( + if (git_buf_joinpath(&ign->dir, ign->dir.ptr, dir) < 0) + return -1; + else + return push_ignore( ign->repo, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE); - - return error; } int git_ignore__pop_dir(git_ignores *ign) @@ -150,7 +136,7 @@ int git_ignore__pop_dir(git_ignores *ign) git_vector_pop(&ign->ign_path); git_buf_rtruncate_at_char(&ign->dir, '/'); } - return GIT_SUCCESS; + return 0; } void git_ignore__free(git_ignores *ignores) @@ -161,54 +147,52 @@ void git_ignore__free(git_ignores *ignores) git_buf_free(&ignores->dir); } -static int ignore_lookup_in_rules( +static bool ignore_lookup_in_rules( git_vector *rules, git_attr_path *path, int *ignored) { unsigned int j; git_attr_fnmatch *match; git_vector_rforeach(rules, j, match) { - if (git_attr_fnmatch__match(match, path) == GIT_SUCCESS) { + if (git_attr_fnmatch__match(match, path)) { *ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0); - return GIT_SUCCESS; + return true; } } - return GIT_ENOTFOUND; + return false; } int git_ignore__lookup(git_ignores *ignores, const char *pathname, int *ignored) { - int error; unsigned int i; git_attr_file *file; git_attr_path path; - if ((error = git_attr_path__init( - &path, pathname, git_repository_workdir(ignores->repo))) < GIT_SUCCESS) - return git__rethrow(error, "Could not get attribute for '%s'", pathname); + if (git_attr_path__init( + &path, pathname, git_repository_workdir(ignores->repo)) < 0) + return -1; - /* first process builtins */ - error = ignore_lookup_in_rules( - &ignores->ign_internal->rules, &path, ignored); - if (error == GIT_SUCCESS) - return error; + /* first process builtins - success means path was found */ + if (ignore_lookup_in_rules( + &ignores->ign_internal->rules, &path, ignored)) + goto cleanup; /* next process files in the path */ git_vector_foreach(&ignores->ign_path, i, file) { - error = ignore_lookup_in_rules(&file->rules, &path, ignored); - if (error == GIT_SUCCESS) - return error; + if (ignore_lookup_in_rules(&file->rules, &path, ignored)) + goto cleanup; } /* last process global ignores */ git_vector_foreach(&ignores->ign_global, i, file) { - error = ignore_lookup_in_rules(&file->rules, &path, ignored); - if (error == GIT_SUCCESS) - return error; + if (ignore_lookup_in_rules(&file->rules, &path, ignored)) + goto cleanup; } *ignored = 0; - return GIT_SUCCESS; +cleanup: + git_attr_path__free(&path); + return 0; } diff --git a/src/index.c b/src/index.c index 5ac99de3e..216ede777 100644 --- a/src/index.c +++ b/src/index.c @@ -135,22 +135,17 @@ int git_index_open(git_index **index_out, const char *index_path) assert(index_out && index_path); - index = git__malloc(sizeof(git_index)); - if (index == NULL) - return GIT_ENOMEM; - - memset(index, 0x0, sizeof(git_index)); + index = git__calloc(1, sizeof(git_index)); + GITERR_CHECK_ALLOC(index); index->index_file_path = git__strdup(index_path); - if (index->index_file_path == NULL) { - git__free(index); - return GIT_ENOMEM; - } + GITERR_CHECK_ALLOC(index->index_file_path); - git_vector_init(&index->entries, 32, index_cmp); + if (git_vector_init(&index->entries, 32, index_cmp) < 0) + return -1; /* Check if index file is stored on disk already */ - if (git_path_exists(index->index_file_path) == 0) + if (git_path_exists(index->index_file_path) == true) index->on_disk = 1; *index_out = index; @@ -215,36 +210,35 @@ void git_index_clear(git_index *index) int git_index_read(git_index *index) { - int error = GIT_SUCCESS, updated; + int error, updated; git_buf buffer = GIT_BUF_INIT; time_t mtime; assert(index->index_file_path); - if (!index->on_disk || git_path_exists(index->index_file_path) < 0) { + if (!index->on_disk || git_path_exists(index->index_file_path) == false) { git_index_clear(index); index->on_disk = 0; - return GIT_SUCCESS; + return 0; } /* We don't want to update the mtime if we fail to parse the index */ mtime = index->last_modified; - error = git_futils_readbuffer_updated(&buffer, index->index_file_path, &mtime, &updated); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to read index"); + error = git_futils_readbuffer_updated( + &buffer, index->index_file_path, &mtime, &updated); + if (error < 0) + return error; if (updated) { git_index_clear(index); error = parse_index(index, buffer.ptr, buffer.size); - if (error == GIT_SUCCESS) + if (!error) index->last_modified = mtime; git_buf_free(&buffer); } - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to parse index"); return error; } @@ -256,23 +250,24 @@ int git_index_write(git_index *index) git_vector_sort(&index->entries); - if ((error = git_filebuf_open(&file, index->index_file_path, GIT_FILEBUF_HASH_CONTENTS)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to write index"); + if ((error = git_filebuf_open( + &file, index->index_file_path, GIT_FILEBUF_HASH_CONTENTS)) < 0) + return error; - if ((error = write_index(index, &file)) < GIT_SUCCESS) { + if ((error = write_index(index, &file)) < 0) { git_filebuf_cleanup(&file); - return git__rethrow(error, "Failed to write index"); + return error; } - if ((error = git_filebuf_commit(&file, GIT_INDEX_FILE_MODE)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to write index"); + if ((error = git_filebuf_commit(&file, GIT_INDEX_FILE_MODE)) < 0) + return error; if (p_stat(index->index_file_path, &indexst) == 0) { index->last_modified = indexst.st_mtime; index->on_disk = 1; } - return GIT_SUCCESS; + return 0; } unsigned int git_index_entrycount(git_index *index) @@ -293,6 +288,20 @@ git_index_entry *git_index_get(git_index *index, unsigned int n) return git_vector_get(&index->entries, n); } +void git_index__init_entry_from_stat(struct stat *st, git_index_entry *entry) +{ + entry->ctime.seconds = (git_time_t)st->st_ctime; + entry->mtime.seconds = (git_time_t)st->st_mtime; + /* entry->mtime.nanoseconds = st->st_mtimensec; */ + /* entry->ctime.nanoseconds = st->st_ctimensec; */ + entry->dev = st->st_rdev; + entry->ino = st->st_ino; + entry->mode = index_create_mode(st->st_mode); + entry->uid = st->st_uid; + entry->gid = st->st_gid; + entry->file_size = st->st_size; +} + static int index_entry_init(git_index_entry **entry_out, git_index *index, const char *rel_path, int stage) { git_index_entry *entry = NULL; @@ -302,25 +311,20 @@ static int index_entry_init(git_index_entry **entry_out, git_index *index, const git_buf full_path = GIT_BUF_INIT; int error; - if (INDEX_OWNER(index) == NULL) - return git__throw(GIT_EBAREINDEX, - "Failed to initialize entry. Repository is bare"); - - if (stage < 0 || stage > 3) - return git__throw(GIT_ERROR, - "Failed to initialize entry. Invalid stage %i", stage); + assert(stage >= 0 && stage <= 3); - workdir = git_repository_workdir(INDEX_OWNER(index)); - if (workdir == NULL) - return git__throw(GIT_EBAREINDEX, - "Failed to initialize entry. Cannot resolved workdir"); + if (INDEX_OWNER(index) == NULL || + (workdir = git_repository_workdir(INDEX_OWNER(index))) == NULL) + { + giterr_set(GITERR_INDEX, + "Could not initialize index entry. Repository is bare"); + return -1; + } - error = git_buf_joinpath(&full_path, workdir, rel_path); - if (error < GIT_SUCCESS) + if ((error = git_buf_joinpath(&full_path, workdir, rel_path)) < 0) return error; - if (p_lstat(full_path.ptr, &st) < 0) { - error = git__throw(GIT_ENOTFOUND, "Failed to initialize entry. '%s' cannot be opened. %s", full_path.ptr, strerror(errno)); + if ((error = git_path_lstat(full_path.ptr, &st)) < 0) { git_buf_free(&full_path); return error; } @@ -332,34 +336,21 @@ static int index_entry_init(git_index_entry **entry_out, git_index *index, const */ /* write the blob to disk and get the oid */ - if ((error = git_blob_create_fromfile(&oid, INDEX_OWNER(index), rel_path)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to initialize index entry"); + if ((error = git_blob_create_fromfile(&oid, INDEX_OWNER(index), rel_path)) < 0) + return error; entry = git__calloc(1, sizeof(git_index_entry)); - if (!entry) - return GIT_ENOMEM; - - entry->ctime.seconds = (git_time_t)st.st_ctime; - entry->mtime.seconds = (git_time_t)st.st_mtime; - /* entry.mtime.nanoseconds = st.st_mtimensec; */ - /* entry.ctime.nanoseconds = st.st_ctimensec; */ - entry->dev= st.st_rdev; - entry->ino = st.st_ino; - entry->mode = index_create_mode(st.st_mode); - entry->uid = st.st_uid; - entry->gid = st.st_gid; - entry->file_size = st.st_size; - entry->oid = oid; + GITERR_CHECK_ALLOC(entry); + git_index__init_entry_from_stat(&st, entry); + + entry->oid = oid; entry->flags |= (stage << GIT_IDXENTRY_STAGESHIFT); entry->path = git__strdup(rel_path); - if (entry->path == NULL) { - git__free(entry); - return GIT_ENOMEM; - } + GITERR_CHECK_ALLOC(entry->path); *entry_out = entry; - return GIT_SUCCESS; + return 0; } static git_index_entry *index_entry_dup(const git_index_entry *source_entry) @@ -394,10 +385,7 @@ static int index_insert(git_index *index, git_index_entry *entry, int replace) int position; git_index_entry **entry_array; - assert(index && entry); - - if (entry->path == NULL) - return git__throw(GIT_EMISSINGOBJDATA, "Failed to insert into index. Entry has no path"); + assert(index && entry && entry->path != NULL); /* make sure that the path length flag is correct */ path_length = strlen(entry->path); @@ -413,12 +401,8 @@ static int index_insert(git_index *index, git_index_entry *entry, int replace) * replacing is not requested: just insert entry at the end; * the index is no longer sorted */ - if (!replace) { - if (git_vector_insert(&index->entries, entry) < GIT_SUCCESS) - return GIT_ENOMEM; - - return GIT_SUCCESS; - } + if (!replace) + return git_vector_insert(&index->entries, entry); /* look if an entry with this path already exists */ position = git_index_find(index, entry->path); @@ -427,12 +411,8 @@ static int index_insert(git_index *index, git_index_entry *entry, int replace) * if no entry exists add the entry at the end; * the index is no longer sorted */ - if (position == GIT_ENOTFOUND) { - if (git_vector_insert(&index->entries, entry) < GIT_SUCCESS) - return GIT_ENOMEM; - - return GIT_SUCCESS; - } + if (position == GIT_ENOTFOUND) + return git_vector_insert(&index->entries, entry); /* exists, replace it */ entry_array = (git_index_entry **) index->entries.contents; @@ -440,7 +420,7 @@ static int index_insert(git_index *index, git_index_entry *entry, int replace) git__free(entry_array[position]); entry_array[position] = entry; - return GIT_SUCCESS; + return 0; } static int index_add(git_index *index, const char *path, int stage, int replace) @@ -448,20 +428,15 @@ static int index_add(git_index *index, const char *path, int stage, int replace) git_index_entry *entry = NULL; int ret; - ret = index_entry_init(&entry, index, path, stage); - if (ret) - goto err; - - ret = index_insert(index, entry, replace); - if (ret) - goto err; + if ((ret = index_entry_init(&entry, index, path, stage)) < 0 || + (ret = index_insert(index, entry, replace)) < 0) + { + index_entry_free(entry); + return ret; + } git_tree_cache_invalidate_path(index->tree, entry->path); - - return ret; -err: - index_entry_free(entry); - return git__rethrow(ret, "Failed to append to index"); + return 0; } int git_index_add(git_index *index, const char *path, int stage) @@ -474,28 +449,23 @@ int git_index_append(git_index *index, const char *path, int stage) return index_add(index, path, stage, 0); } -static int index_add2(git_index *index, const git_index_entry *source_entry, - int replace) +static int index_add2( + git_index *index, const git_index_entry *source_entry, int replace) { git_index_entry *entry = NULL; int ret; entry = index_entry_dup(source_entry); - if (entry == NULL) { - ret = GIT_ENOMEM; - goto err; - } + if (entry == NULL) + return -1; - ret = index_insert(index, entry, replace); - if (ret) - goto err; + if ((ret = index_insert(index, entry, replace)) < 0) { + index_entry_free(entry); + return ret; + } git_tree_cache_invalidate_path(index->tree, entry->path); - - return ret; -err: - index_entry_free(entry); - return git__rethrow(ret, "Failed to append to index"); + return 0; } int git_index_add2(git_index *index, const git_index_entry *source_entry) @@ -514,13 +484,14 @@ int git_index_remove(git_index *index, int position) git_index_entry *entry; git_vector_sort(&index->entries); + entry = git_vector_get(&index->entries, position); if (entry != NULL) git_tree_cache_invalidate_path(index->tree, entry->path); error = git_vector_remove(&index->entries, (unsigned int)position); - if (error == GIT_SUCCESS) + if (!error) index_entry_free(entry); return error; @@ -536,7 +507,8 @@ void git_index_uniq(git_index *index) git_vector_uniq(&index->entries); } -const git_index_entry_unmerged *git_index_get_unmerged_bypath(git_index *index, const char *path) +const git_index_entry_unmerged *git_index_get_unmerged_bypath( + git_index *index, const char *path) { int pos; assert(index && path); @@ -544,75 +516,87 @@ const git_index_entry_unmerged *git_index_get_unmerged_bypath(git_index *index, if (!index->unmerged.length) return NULL; - if ((pos = git_vector_bsearch2(&index->unmerged, unmerged_srch, path)) < GIT_SUCCESS) + if ((pos = git_vector_bsearch2(&index->unmerged, unmerged_srch, path)) < 0) return NULL; return git_vector_get(&index->unmerged, pos); } -const git_index_entry_unmerged *git_index_get_unmerged_byindex(git_index *index, unsigned int n) +const git_index_entry_unmerged *git_index_get_unmerged_byindex( + git_index *index, unsigned int n) { assert(index); return git_vector_get(&index->unmerged, n); } +static int index_error_invalid(const char *message) +{ + giterr_set(GITERR_INDEX, "Invalid data in index - %s", message); + return -1; +} + static int read_unmerged(git_index *index, const char *buffer, size_t size) { const char *endptr; size_t len; int i; - git_vector_init(&index->unmerged, 16, unmerged_cmp); + if (git_vector_init(&index->unmerged, 16, unmerged_cmp) < 0) + return -1; while (size) { git_index_entry_unmerged *lost; len = strlen(buffer) + 1; if (size <= len) - return git__throw(GIT_ERROR, "Failed to read unmerged entries"); + return index_error_invalid("reading unmerged entries"); - if ((lost = git__malloc(sizeof(git_index_entry_unmerged))) == NULL) - return GIT_ENOMEM; + lost = git__malloc(sizeof(git_index_entry_unmerged)); + GITERR_CHECK_ALLOC(lost); - if (git_vector_insert(&index->unmerged, lost) < GIT_SUCCESS) - return git__throw(GIT_ERROR, "Failed to read unmerged entries"); + if (git_vector_insert(&index->unmerged, lost) < 0) + return -1; + /* read NUL-terminated pathname for entry */ lost->path = git__strdup(buffer); - if (!lost->path) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(lost->path); size -= len; buffer += len; + /* read 3 ASCII octal numbers for stage entries */ for (i = 0; i < 3; i++) { int tmp; - if (git__strtol32(&tmp, buffer, &endptr, 8) < GIT_SUCCESS || - !endptr || endptr == buffer || *endptr || (unsigned)tmp > UINT_MAX) - return GIT_ERROR; + if (git__strtol32(&tmp, buffer, &endptr, 8) < 0 || + !endptr || endptr == buffer || *endptr || + (unsigned)tmp > UINT_MAX) + return index_error_invalid("reading unmerged entry stage"); lost->mode[i] = tmp; len = (endptr + 1) - buffer; if (size <= len) - return git__throw(GIT_ERROR, "Failed to read unmerged entries"); + return index_error_invalid("reading unmerged entry stage"); size -= len; buffer += len; } + /* read up to 3 OIDs for stage entries */ for (i = 0; i < 3; i++) { if (!lost->mode[i]) continue; if (size < 20) - return git__throw(GIT_ERROR, "Failed to read unmerged entries"); + return index_error_invalid("reading unmerged entry oid"); + git_oid_fromraw(&lost->oid[i], (const unsigned char *) buffer); size -= 20; buffer += 20; } } - return GIT_SUCCESS; + return 0; } static size_t read_entry(git_index_entry *dest, const void *buffer, size_t buffer_size) @@ -658,7 +642,7 @@ static size_t read_entry(git_index_entry *dest, const void *buffer, size_t buffe path_end = memchr(path_ptr, '\0', buffer_size); if (path_end == NULL) - return 0; + return 0; path_length = path_end - path_ptr; } @@ -683,15 +667,15 @@ static int read_header(struct index_header *dest, const void *buffer) dest->signature = ntohl(source->signature); if (dest->signature != INDEX_HEADER_SIG) - return GIT_EOBJCORRUPTED; + return index_error_invalid("incorrect header signature"); dest->version = ntohl(source->version); if (dest->version != INDEX_VERSION_NUMBER_EXT && dest->version != INDEX_VERSION_NUMBER) - return GIT_EOBJCORRUPTED; + return index_error_invalid("incorrect header version"); dest->entry_count = ntohl(source->entry_count); - return GIT_SUCCESS; + return 0; } static size_t read_extension(git_index *index, const char *buffer, size_t buffer_size) @@ -714,10 +698,10 @@ static size_t read_extension(git_index *index, const char *buffer, size_t buffer if (dest.signature[0] >= 'A' && dest.signature[0] <= 'Z') { /* tree cache */ if (memcmp(dest.signature, INDEX_EXT_TREECACHE_SIG, 4) == 0) { - if (git_tree_cache_read(&index->tree, buffer + 8, dest.extension_size) < GIT_SUCCESS) + if (git_tree_cache_read(&index->tree, buffer + 8, dest.extension_size) < 0) return 0; } else if (memcmp(dest.signature, INDEX_EXT_UNMERGED_SIG, 4) == 0) { - if (read_unmerged(index, buffer + 8, dest.extension_size) < GIT_SUCCESS) + if (read_unmerged(index, buffer + 8, dest.extension_size) < 0) return 0; } /* else, unsupported extension. We cannot parse this, but we can skip @@ -739,21 +723,21 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) #define seek_forward(_increase) { \ if (_increase >= buffer_size) \ - return git__throw(GIT_EOBJCORRUPTED, "Failed to seek forward. Buffer size exceeded"); \ + return index_error_invalid("ran out of data while parsing"); \ buffer += _increase; \ buffer_size -= _increase;\ } if (buffer_size < INDEX_HEADER_SIZE + INDEX_FOOTER_SIZE) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Buffer too small"); + return index_error_invalid("insufficient buffer space"); /* Precalculate the SHA1 of the files's contents -- we'll match it to * the provided SHA1 in the footer */ git_hash_buf(&checksum_calculated, buffer, buffer_size - INDEX_FOOTER_SIZE); /* Parse header */ - if (read_header(&header, buffer) < GIT_SUCCESS) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Header is corrupted"); + if (read_header(&header, buffer) < 0) + return -1; seek_forward(INDEX_HEADER_SIZE); @@ -765,23 +749,22 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) git_index_entry *entry; entry = git__malloc(sizeof(git_index_entry)); - if (entry == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(entry); entry_size = read_entry(entry, buffer, buffer_size); /* 0 bytes read means an object corruption */ if (entry_size == 0) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Entry size is zero"); + return index_error_invalid("invalid entry"); - if (git_vector_insert(&index->entries, entry) < GIT_SUCCESS) - return GIT_ENOMEM; + if (git_vector_insert(&index->entries, entry) < 0) + return -1; seek_forward(entry_size); } if (i != header.entry_count) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Header entries changed while parsing"); + return index_error_invalid("header entries changed while parsing"); /* There's still space for some extensions! */ while (buffer_size > INDEX_FOOTER_SIZE) { @@ -791,43 +774,43 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) /* see if we have read any bytes from the extension */ if (extension_size == 0) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Extension size is zero"); + return index_error_invalid("extension size is zero"); seek_forward(extension_size); } if (buffer_size != INDEX_FOOTER_SIZE) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Buffer size does not match index footer size"); + return index_error_invalid("buffer size does not match index footer size"); /* 160-bit SHA-1 over the content of the index file before this checksum. */ git_oid_fromraw(&checksum_expected, (const unsigned char *)buffer); if (git_oid_cmp(&checksum_calculated, &checksum_expected) != 0) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse index. Calculated checksum does not match expected checksum"); + return index_error_invalid("calculated checksum does not match expected"); #undef seek_forward /* force sorting in the vector: the entries are * assured to be sorted on the index */ index->entries.sorted = 1; - return GIT_SUCCESS; + return 0; } static int is_index_extended(git_index *index) { unsigned int i, extended; + git_index_entry *entry; extended = 0; - for (i = 0; i < index->entries.length; ++i) { - git_index_entry *entry; - entry = git_vector_get(&index->entries, i); + git_vector_foreach(&index->entries, i, entry) { entry->flags &= ~GIT_IDXENTRY_EXTENDED; if (entry->flags_extended & GIT_IDXENTRY_EXTENDED_FLAGS) { extended++; entry->flags |= GIT_IDXENTRY_EXTENDED; } } + return extended; } @@ -845,8 +828,8 @@ static int write_disk_entry(git_filebuf *file, git_index_entry *entry) else disk_size = short_entry_size(path_len); - if (git_filebuf_reserve(file, &mem, disk_size) < GIT_SUCCESS) - return GIT_ENOMEM; + if (git_filebuf_reserve(file, &mem, disk_size) < 0) + return -1; ondisk = (struct entry_short *)mem; @@ -888,7 +871,7 @@ static int write_disk_entry(git_filebuf *file, git_index_entry *entry) memcpy(path, entry->path, path_len); - return GIT_SUCCESS; + return 0; } static int write_entries(git_index *index, git_filebuf *file) @@ -898,16 +881,15 @@ static int write_entries(git_index *index, git_filebuf *file) for (i = 0; i < index->entries.length; ++i) { git_index_entry *entry; entry = git_vector_get(&index->entries, i); - if (write_disk_entry(file, entry) < GIT_SUCCESS) - return GIT_ENOMEM; + if (write_disk_entry(file, entry) < 0) + return -1; } - return GIT_SUCCESS; + return 0; } static int write_index(git_index *index, git_filebuf *file) { - int error = GIT_SUCCESS; git_oid hash_final; struct index_header header; @@ -922,11 +904,11 @@ static int write_index(git_index *index, git_filebuf *file) header.version = htonl(is_extended ? INDEX_VERSION_NUMBER_EXT : INDEX_VERSION_NUMBER); header.entry_count = htonl(index->entries.length); - git_filebuf_write(file, &header, sizeof(struct index_header)); + if (git_filebuf_write(file, &header, sizeof(struct index_header)) < 0) + return -1; - error = write_entries(index, file); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to write index"); + if (write_entries(index, file) < 0) + return -1; /* TODO: write extensions (tree cache) */ @@ -934,9 +916,7 @@ static int write_index(git_index *index, git_filebuf *file) git_filebuf_hash(&hash_final, file); /* write it at the end of the file */ - git_filebuf_write(file, hash_final.id, GIT_OID_RAWSZ); - - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write index"); + return git_filebuf_write(file, hash_final.id, GIT_OID_RAWSZ); } int git_index_entry_stage(const git_index_entry *entry) @@ -946,36 +926,30 @@ int git_index_entry_stage(const git_index_entry *entry) static int read_tree_cb(const char *root, git_tree_entry *tentry, void *data) { - int ret = GIT_SUCCESS; git_index *index = data; git_index_entry *entry = NULL; git_buf path = GIT_BUF_INIT; if (entry_is_tree(tentry)) - goto exit; + return 0; - ret = git_buf_joinpath(&path, root, tentry->filename); - if (ret < GIT_SUCCESS) - goto exit; + if (git_buf_joinpath(&path, root, tentry->filename) < 0) + return -1; entry = git__calloc(1, sizeof(git_index_entry)); - if (!entry) { - ret = GIT_ENOMEM; - goto exit; - } + GITERR_CHECK_ALLOC(entry); entry->mode = tentry->attr; entry->oid = tentry->oid; entry->path = git_buf_detach(&path); - - ret = index_insert(index, entry, 0); - -exit: git_buf_free(&path); - if (ret < GIT_SUCCESS) + if (index_insert(index, entry, 0) < 0) { index_entry_free(entry); - return ret; + return -1; + } + + return 0; } int git_index_read_tree(git_index *index, git_tree *tree) diff --git a/src/index.h b/src/index.h index 4f036526f..e745c8f69 100644 --- a/src/index.h +++ b/src/index.h @@ -31,4 +31,6 @@ struct git_index { git_vector unmerged; }; +extern void git_index__init_entry_from_stat(struct stat *st, git_index_entry *entry); + #endif diff --git a/src/indexer.c b/src/indexer.c index de1e5dc52..d2e492c39 100644 --- a/src/indexer.c +++ b/src/indexer.c @@ -30,8 +30,6 @@ struct entry { struct git_indexer { struct git_pack_file *pack; - struct stat st; - struct git_pack_header hdr; size_t nr_objects; git_vector objects; git_filebuf file; @@ -39,27 +37,89 @@ struct git_indexer { git_oid hash; }; +struct git_indexer_stream { + unsigned int parsed_header :1, + opened_pack; + struct git_pack_file *pack; + git_filebuf pack_file; + git_filebuf index_file; + git_off_t off; + size_t nr_objects; + git_vector objects; + git_vector deltas; + unsigned int fanout[256]; + git_oid hash; +}; + +struct delta_info { + git_off_t delta_off; +}; + const git_oid *git_indexer_hash(git_indexer *idx) { return &idx->hash; } -static int parse_header(git_indexer *idx) +const git_oid *git_indexer_stream_hash(git_indexer_stream *idx) +{ + return &idx->hash; +} + +static int open_pack(struct git_pack_file **out, const char *filename) +{ + size_t namelen; + struct git_pack_file *pack; + struct stat st; + int fd; + + namelen = strlen(filename); + pack = git__calloc(1, sizeof(struct git_pack_file) + namelen + 1); + GITERR_CHECK_ALLOC(pack); + + memcpy(pack->pack_name, filename, namelen + 1); + + if (p_stat(filename, &st) < 0) { + giterr_set(GITERR_OS, "Failed to stat packfile."); + goto cleanup; + } + + if ((fd = p_open(pack->pack_name, O_RDONLY)) < 0) { + giterr_set(GITERR_OS, "Failed to open packfile."); + goto cleanup; + } + + pack->mwf.fd = fd; + pack->mwf.size = (git_off_t)st.st_size; + + *out = pack; + return 0; + +cleanup: + git__free(pack); + return -1; +} + +static int parse_header(struct git_pack_header *hdr, struct git_pack_file *pack) { int error; /* Verify we recognize this pack file format. */ - if ((error = p_read(idx->pack->mwf.fd, &idx->hdr, sizeof(idx->hdr))) < GIT_SUCCESS) - return git__rethrow(error, "Failed to read in pack header"); - - if (idx->hdr.hdr_signature != ntohl(PACK_SIGNATURE)) - return git__throw(GIT_EOBJCORRUPTED, "Wrong pack signature"); + if ((error = p_read(pack->mwf.fd, hdr, sizeof(*hdr))) < 0) { + giterr_set(GITERR_OS, "Failed to read in pack header"); + return error; + } - if (!pack_version_ok(idx->hdr.hdr_version)) - return git__throw(GIT_EOBJCORRUPTED, "Wrong pack version"); + if (hdr->hdr_signature != ntohl(PACK_SIGNATURE)) { + giterr_set(GITERR_INVALID, "Wrong pack signature"); + return -1; + } + if (!pack_version_ok(hdr->hdr_version)) { + giterr_set(GITERR_INVALID, "Wrong pack version"); + return -1; + } - return GIT_SUCCESS; + return 0; } static int objects_cmp(const void *a, const void *b) @@ -78,78 +138,484 @@ static int cache_cmp(const void *a, const void *b) return git_oid_cmp(&ea->sha1, &eb->sha1); } +int git_indexer_stream_new(git_indexer_stream **out, const char *prefix) +{ + git_indexer_stream *idx; + git_buf path = GIT_BUF_INIT; + static const char suff[] = "/objects/pack/pack-received"; + int error; -int git_indexer_new(git_indexer **out, const char *packname) + idx = git__calloc(1, sizeof(git_indexer_stream)); + GITERR_CHECK_ALLOC(idx); + + error = git_buf_joinpath(&path, prefix, suff); + if (error < 0) + goto cleanup; + + error = git_filebuf_open(&idx->pack_file, path.ptr, + GIT_FILEBUF_TEMPORARY | GIT_FILEBUF_DO_NOT_BUFFER); + git_buf_free(&path); + if (error < 0) + goto cleanup; + + *out = idx; + return 0; + +cleanup: + git_buf_free(&path); + git_filebuf_cleanup(&idx->pack_file); + git__free(idx); + return -1; +} + +/* Try to store the delta so we can try to resolve it later */ +static int store_delta(git_indexer_stream *idx) { - git_indexer *idx; - size_t namelen; - int ret, error; + git_otype type; + git_mwindow *w = NULL; + git_mwindow_file *mwf = &idx->pack->mwf; + git_off_t entry_start = idx->off; + struct delta_info *delta; + size_t entry_size; + git_rawobj obj; + int error; - assert(out && packname); + /* + * ref-delta objects can refer to object that we haven't + * found yet, so give it another opportunity + */ + if (git_packfile_unpack_header(&entry_size, &type, mwf, &w, &idx->off) < 0) + return -1; - if (git_path_root(packname) < 0) - return git__throw(GIT_EINVALIDPATH, "Path is not absolute"); + git_mwindow_close(&w); - idx = git__malloc(sizeof(git_indexer)); - if (idx == NULL) - return GIT_ENOMEM; + /* If it's not a delta, mark it as failure, we can't do anything with it */ + if (type != GIT_OBJ_REF_DELTA && type != GIT_OBJ_OFS_DELTA) + return -1; - memset(idx, 0x0, sizeof(*idx)); + if (type == GIT_OBJ_REF_DELTA) { + idx->off += GIT_OID_RAWSZ; + } else { + git_off_t base_off; - namelen = strlen(packname); - idx->pack = git__malloc(sizeof(struct git_pack_file) + namelen + 1); - if (idx->pack == NULL) { - error = GIT_ENOMEM; - goto cleanup; + base_off = get_delta_base(idx->pack, &w, &idx->off, type, entry_start); + git_mwindow_close(&w); + if (base_off < 0) + return (int)base_off; + } + + error = packfile_unpack_compressed(&obj, idx->pack, &w, &idx->off, entry_size, type); + if (error == GIT_ESHORTBUFFER) { + idx->off = entry_start; + return GIT_ESHORTBUFFER; + } else if (error < 0){ + return -1; + } + + delta = git__calloc(1, sizeof(struct delta_info)); + GITERR_CHECK_ALLOC(delta); + delta->delta_off = entry_start; + + git__free(obj.data); + + if (git_vector_insert(&idx->deltas, delta) < 0) + return -1; + + return 0; +} + +static int hash_and_save(git_indexer_stream *idx, git_rawobj *obj, git_off_t entry_start) +{ + int i; + git_oid oid; + void *packed; + size_t entry_size; + unsigned int left; + struct entry *entry; + git_mwindow *w = NULL; + git_mwindow_file *mwf = &idx->pack->mwf; + struct git_pack_entry *pentry; + + entry = git__calloc(1, sizeof(*entry)); + GITERR_CHECK_ALLOC(entry); + + if (entry_start > UINT31_MAX) { + entry->offset = UINT32_MAX; + entry->offset_long = entry_start; + } else { + entry->offset = (uint32_t)entry_start; + } + + /* FIXME: Parse the object instead of hashing it */ + if (git_odb__hashobj(&oid, obj) < 0) { + giterr_set(GITERR_INVALID, "Failed to hash object"); + return -1; + } + + pentry = git__malloc(sizeof(struct git_pack_entry)); + GITERR_CHECK_ALLOC(pentry); + + git_oid_cpy(&pentry->sha1, &oid); + pentry->offset = entry_start; + if (git_vector_insert(&idx->pack->cache, pentry) < 0) + goto on_error; + + git_oid_cpy(&entry->oid, &oid); + entry->crc = crc32(0L, Z_NULL, 0); + + entry_size = (size_t)(idx->off - entry_start); + packed = git_mwindow_open(mwf, &w, entry_start, entry_size, &left); + if (packed == NULL) + goto on_error; + + entry->crc = htonl(crc32(entry->crc, packed, (uInt)entry_size)); + git_mwindow_close(&w); + + /* Add the object to the list */ + if (git_vector_insert(&idx->objects, entry) < 0) + goto on_error; + + for (i = oid.id[0]; i < 256; ++i) { + idx->fanout[i]++; + } + + return 0; + +on_error: + git__free(entry); + git__free(pentry); + git__free(obj->data); + return -1; +} + +int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t size, git_indexer_stats *stats) +{ + int error; + struct git_pack_header hdr; + size_t processed = stats->processed; + git_mwindow_file *mwf = &idx->pack->mwf; + + assert(idx && data && stats); + + if (git_filebuf_write(&idx->pack_file, data, size) < 0) + return -1; + + /* Make sure we set the new size of the pack */ + if (idx->opened_pack) { + idx->pack->mwf.size += size; + //printf("\nadding %zu for %zu\n", size, idx->pack->mwf.size); + } else { + if (open_pack(&idx->pack, idx->pack_file.path_lock) < 0) + return -1; + idx->opened_pack = 1; + mwf = &idx->pack->mwf; + if (git_mwindow_file_register(&idx->pack->mwf) < 0) + return -1; + + return 0; + } + + if (!idx->parsed_header) { + if ((unsigned)idx->pack->mwf.size < sizeof(hdr)) + return 0; + + if (parse_header(&hdr, idx->pack) < 0) + return -1; + + idx->parsed_header = 1; + idx->nr_objects = ntohl(hdr.hdr_entries); + idx->off = sizeof(struct git_pack_header); + + /* for now, limit to 2^32 objects */ + assert(idx->nr_objects == (size_t)((unsigned int)idx->nr_objects)); + + if (git_vector_init(&idx->pack->cache, (unsigned int)idx->nr_objects, cache_cmp) < 0) + return -1; + + idx->pack->has_cache = 1; + if (git_vector_init(&idx->objects, (unsigned int)idx->nr_objects, objects_cmp) < 0) + return -1; + + if (git_vector_init(&idx->deltas, (unsigned int)(idx->nr_objects / 2), NULL) < 0) + return -1; + + stats->total = (unsigned int)idx->nr_objects; + stats->processed = 0; + } + + /* Now that we have data in the pack, let's try to parse it */ + + /* As the file grows any windows we try to use will be out of date */ + git_mwindow_free_all(mwf); + while (processed < idx->nr_objects) { + git_rawobj obj; + git_off_t entry_start = idx->off; + + if (idx->pack->mwf.size <= idx->off + 20) + return 0; + + error = git_packfile_unpack(&obj, idx->pack, &idx->off); + if (error == GIT_ESHORTBUFFER) { + idx->off = entry_start; + return 0; + } + + if (error < 0) { + idx->off = entry_start; + error = store_delta(idx); + if (error == GIT_ESHORTBUFFER) + return 0; + if (error < 0) + return error; + + continue; + } + + if (hash_and_save(idx, &obj, entry_start) < 0) + goto on_error; + + git__free(obj.data); + + stats->processed = (unsigned int)++processed; } - memset(idx->pack, 0x0, sizeof(struct git_pack_file)); - memcpy(idx->pack->pack_name, packname, namelen + 1); + return 0; - ret = p_stat(packname, &idx->st); - if (ret < 0) { - if (errno == ENOENT) - error = git__throw(GIT_ENOTFOUND, "Failed to stat packfile. File not found"); +on_error: + git_mwindow_free_all(mwf); + return -1; +} + +static int index_path_stream(git_buf *path, git_indexer_stream *idx, const char *suffix) +{ + const char prefix[] = "pack-"; + size_t slash = (size_t)path->size; + + /* search backwards for '/' */ + while (slash > 0 && path->ptr[slash - 1] != '/') + slash--; + + if (git_buf_grow(path, slash + 1 + strlen(prefix) + + GIT_OID_HEXSZ + strlen(suffix) + 1) < 0) + return -1; + + git_buf_truncate(path, slash); + git_buf_puts(path, prefix); + git_oid_fmt(path->ptr + git_buf_len(path), &idx->hash); + path->size += GIT_OID_HEXSZ; + git_buf_puts(path, suffix); + + return git_buf_oom(path) ? -1 : 0; +} + +static int resolve_deltas(git_indexer_stream *idx, git_indexer_stats *stats) +{ + unsigned int i; + struct delta_info *delta; + + git_vector_foreach(&idx->deltas, i, delta) { + git_rawobj obj; + + idx->off = delta->delta_off; + if (git_packfile_unpack(&obj, idx->pack, &idx->off) < 0) + return -1; + + if (hash_and_save(idx, &obj, delta->delta_off) < 0) + return -1; + + git__free(obj.data); + stats->processed++; + } + + return 0; +} + +int git_indexer_stream_finalize(git_indexer_stream *idx, git_indexer_stats *stats) +{ + git_mwindow *w = NULL; + unsigned int i, long_offsets = 0, left; + struct git_pack_idx_header hdr; + git_buf filename = GIT_BUF_INIT; + struct entry *entry; + void *packfile_hash; + git_oid file_hash; + SHA_CTX ctx; + + if (idx->deltas.length > 0) + if (resolve_deltas(idx, stats) < 0) + return -1; + + git_vector_sort(&idx->objects); + + git_buf_sets(&filename, idx->pack->pack_name); + git_buf_truncate(&filename, filename.size - strlen("pack")); + git_buf_puts(&filename, "idx"); + if (git_buf_oom(&filename)) + return -1; + + if (git_filebuf_open(&idx->index_file, filename.ptr, GIT_FILEBUF_HASH_CONTENTS) < 0) + goto on_error; + + /* Write out the header */ + hdr.idx_signature = htonl(PACK_IDX_SIGNATURE); + hdr.idx_version = htonl(2); + git_filebuf_write(&idx->index_file, &hdr, sizeof(hdr)); + + /* Write out the fanout table */ + for (i = 0; i < 256; ++i) { + uint32_t n = htonl(idx->fanout[i]); + git_filebuf_write(&idx->index_file, &n, sizeof(n)); + } + + /* Write out the object names (SHA-1 hashes) */ + SHA1_Init(&ctx); + git_vector_foreach(&idx->objects, i, entry) { + git_filebuf_write(&idx->index_file, &entry->oid, sizeof(git_oid)); + SHA1_Update(&ctx, &entry->oid, GIT_OID_RAWSZ); + } + SHA1_Final(idx->hash.id, &ctx); + + /* Write out the CRC32 values */ + git_vector_foreach(&idx->objects, i, entry) { + git_filebuf_write(&idx->index_file, &entry->crc, sizeof(uint32_t)); + } + + /* Write out the offsets */ + git_vector_foreach(&idx->objects, i, entry) { + uint32_t n; + + if (entry->offset == UINT32_MAX) + n = htonl(0x80000000 | long_offsets++); else - error = git__throw(GIT_EOSERR, "Failed to stat packfile."); + n = htonl(entry->offset); - goto cleanup; + git_filebuf_write(&idx->index_file, &n, sizeof(uint32_t)); } - ret = p_open(idx->pack->pack_name, O_RDONLY); - if (ret < 0) { - error = git__throw(GIT_EOSERR, "Failed to open packfile"); - goto cleanup; + /* Write out the long offsets */ + git_vector_foreach(&idx->objects, i, entry) { + uint32_t split[2]; + + if (entry->offset != UINT32_MAX) + continue; + + split[0] = htonl(entry->offset_long >> 32); + split[1] = htonl(entry->offset_long & 0xffffffff); + + git_filebuf_write(&idx->index_file, &split, sizeof(uint32_t) * 2); } - idx->pack->mwf.fd = ret; - idx->pack->mwf.size = (git_off_t)idx->st.st_size; + /* Write out the packfile trailer */ + packfile_hash = git_mwindow_open(&idx->pack->mwf, &w, idx->pack->mwf.size - GIT_OID_RAWSZ, GIT_OID_RAWSZ, &left); + if (packfile_hash == NULL) { + git_mwindow_close(&w); + goto on_error; + } - error = parse_header(idx); - if (error < GIT_SUCCESS) { - error = git__rethrow(error, "Failed to parse packfile header"); - goto cleanup; + memcpy(&file_hash, packfile_hash, GIT_OID_RAWSZ); + git_mwindow_close(&w); + + git_filebuf_write(&idx->index_file, &file_hash, sizeof(git_oid)); + + /* Write out the packfile trailer to the idx file as well */ + if (git_filebuf_hash(&file_hash, &idx->index_file) < 0) + goto on_error; + + git_filebuf_write(&idx->index_file, &file_hash, sizeof(git_oid)); + + /* Figure out what the final name should be */ + if (index_path_stream(&filename, idx, ".idx") < 0) + goto on_error; + + /* Commit file */ + if (git_filebuf_commit_at(&idx->index_file, filename.ptr, GIT_PACK_FILE_MODE) < 0) + goto on_error; + + git_mwindow_free_all(&idx->pack->mwf); + + if (index_path_stream(&filename, idx, ".pack") < 0) + goto on_error; + /* And don't forget to rename the packfile to its new place. */ + if (git_filebuf_commit_at(&idx->pack_file, filename.ptr, GIT_PACK_FILE_MODE) < 0) + return -1; + + git_buf_free(&filename); + return 0; + +on_error: + git_mwindow_free_all(&idx->pack->mwf); + git_filebuf_cleanup(&idx->index_file); + git_buf_free(&filename); + return -1; +} + +void git_indexer_stream_free(git_indexer_stream *idx) +{ + unsigned int i; + struct entry *e; + struct git_pack_entry *pe; + struct delta_info *delta; + + if (idx == NULL) + return; + + p_close(idx->pack->mwf.fd); + git_vector_foreach(&idx->objects, i, e) + git__free(e); + git_vector_free(&idx->objects); + git_vector_foreach(&idx->pack->cache, i, pe) + git__free(pe); + git_vector_free(&idx->pack->cache); + git_vector_foreach(&idx->deltas, i, delta) + git__free(delta); + git_vector_free(&idx->deltas); + git__free(idx->pack); + git__free(idx); +} + +int git_indexer_new(git_indexer **out, const char *packname) +{ + git_indexer *idx; + struct git_pack_header hdr; + int error; + + assert(out && packname); + + if (git_path_root(packname) < 0) { + giterr_set(GITERR_INVALID, "Path is not absolute"); + return -1; } - idx->nr_objects = ntohl(idx->hdr.hdr_entries); + idx = git__calloc(1, sizeof(git_indexer)); + GITERR_CHECK_ALLOC(idx); + + open_pack(&idx->pack, packname); + + if ((error = parse_header(&hdr, idx->pack)) < 0) + goto cleanup; + + idx->nr_objects = ntohl(hdr.hdr_entries); - error = git_vector_init(&idx->pack->cache, idx->nr_objects, cache_cmp); - if (error < GIT_SUCCESS) + /* for now, limit to 2^32 objects */ + assert(idx->nr_objects == (size_t)((unsigned int)idx->nr_objects)); + + error = git_vector_init(&idx->pack->cache, (unsigned int)idx->nr_objects, cache_cmp); + if (error < 0) goto cleanup; idx->pack->has_cache = 1; - error = git_vector_init(&idx->objects, idx->nr_objects, objects_cmp); - if (error < GIT_SUCCESS) + error = git_vector_init(&idx->objects, (unsigned int)idx->nr_objects, objects_cmp); + if (error < 0) goto cleanup; *out = idx; - return GIT_SUCCESS; + return 0; cleanup: git_indexer_free(idx); - return error; + return -1; } static int index_path(git_buf *path, git_indexer *idx) @@ -162,16 +628,16 @@ static int index_path(git_buf *path, git_indexer *idx) slash--; if (git_buf_grow(path, slash + 1 + strlen(prefix) + - GIT_OID_HEXSZ + strlen(suffix) + 1) < GIT_SUCCESS) - return GIT_ENOMEM; + GIT_OID_HEXSZ + strlen(suffix) + 1) < 0) + return -1; git_buf_truncate(path, slash); git_buf_puts(path, prefix); - git_oid_fmt(path->ptr + path->size, &idx->hash); + git_oid_fmt(path->ptr + git_buf_len(path), &idx->hash); path->size += GIT_OID_HEXSZ; git_buf_puts(path, suffix); - return git_buf_lasterror(path); + return git_buf_oom(path) ? -1 : 0; } int git_indexer_write(git_indexer *idx) @@ -191,26 +657,25 @@ int git_indexer_write(git_indexer *idx) git_buf_sets(&filename, idx->pack->pack_name); git_buf_truncate(&filename, filename.size - strlen("pack")); git_buf_puts(&filename, "idx"); - - if ((error = git_buf_lasterror(&filename)) < GIT_SUCCESS) - goto cleanup; + if (git_buf_oom(&filename)) + return -1; error = git_filebuf_open(&idx->file, filename.ptr, GIT_FILEBUF_HASH_CONTENTS); - if (error < GIT_SUCCESS) + if (error < 0) goto cleanup; /* Write out the header */ hdr.idx_signature = htonl(PACK_IDX_SIGNATURE); hdr.idx_version = htonl(2); error = git_filebuf_write(&idx->file, &hdr, sizeof(hdr)); - if (error < GIT_SUCCESS) + if (error < 0) goto cleanup; /* Write out the fanout table */ for (i = 0; i < 256; ++i) { uint32_t n = htonl(idx->fanout[i]); error = git_filebuf_write(&idx->file, &n, sizeof(n)); - if (error < GIT_SUCCESS) + if (error < 0) goto cleanup; } @@ -219,7 +684,7 @@ int git_indexer_write(git_indexer *idx) git_vector_foreach(&idx->objects, i, entry) { error = git_filebuf_write(&idx->file, &entry->oid, sizeof(git_oid)); SHA1_Update(&ctx, &entry->oid, GIT_OID_RAWSZ); - if (error < GIT_SUCCESS) + if (error < 0) goto cleanup; } SHA1_Final(idx->hash.id, &ctx); @@ -227,7 +692,7 @@ int git_indexer_write(git_indexer *idx) /* Write out the CRC32 values */ git_vector_foreach(&idx->objects, i, entry) { error = git_filebuf_write(&idx->file, &entry->crc, sizeof(uint32_t)); - if (error < GIT_SUCCESS) + if (error < 0) goto cleanup; } @@ -241,7 +706,7 @@ int git_indexer_write(git_indexer *idx) n = htonl(entry->offset); error = git_filebuf_write(&idx->file, &n, sizeof(uint32_t)); - if (error < GIT_SUCCESS) + if (error < 0) goto cleanup; } @@ -256,16 +721,16 @@ int git_indexer_write(git_indexer *idx) split[1] = htonl(entry->offset_long & 0xffffffff); error = git_filebuf_write(&idx->file, &split, sizeof(uint32_t) * 2); - if (error < GIT_SUCCESS) + if (error < 0) goto cleanup; } /* Write out the packfile trailer */ - packfile_hash = git_mwindow_open(&idx->pack->mwf, &w, idx->st.st_size - GIT_OID_RAWSZ, GIT_OID_RAWSZ, &left); + packfile_hash = git_mwindow_open(&idx->pack->mwf, &w, idx->pack->mwf.size - GIT_OID_RAWSZ, GIT_OID_RAWSZ, &left); git_mwindow_close(&w); if (packfile_hash == NULL) { - error = git__rethrow(GIT_ENOMEM, "Failed to open window to packfile hash"); + error = -1; goto cleanup; } @@ -274,19 +739,21 @@ int git_indexer_write(git_indexer *idx) git_mwindow_close(&w); error = git_filebuf_write(&idx->file, &file_hash, sizeof(git_oid)); + if (error < 0) + goto cleanup; /* Write out the index sha */ error = git_filebuf_hash(&file_hash, &idx->file); - if (error < GIT_SUCCESS) + if (error < 0) goto cleanup; error = git_filebuf_write(&idx->file, &file_hash, sizeof(git_oid)); - if (error < GIT_SUCCESS) + if (error < 0) goto cleanup; /* Figure out what the final name should be */ error = index_path(&filename, idx); - if (error < GIT_SUCCESS) + if (error < 0) goto cleanup; /* Commit file */ @@ -294,7 +761,7 @@ int git_indexer_write(git_indexer *idx) cleanup: git_mwindow_free_all(&idx->pack->mwf); - if (error < GIT_SUCCESS) + if (error < 0) git_filebuf_cleanup(&idx->file); git_buf_free(&filename); @@ -304,7 +771,7 @@ cleanup: int git_indexer_run(git_indexer *idx, git_indexer_stats *stats) { git_mwindow_file *mwf; - off_t off = sizeof(struct git_pack_header); + git_off_t off = sizeof(struct git_pack_header); int error; struct entry *entry; unsigned int left, processed; @@ -313,10 +780,10 @@ int git_indexer_run(git_indexer *idx, git_indexer_stats *stats) mwf = &idx->pack->mwf; error = git_mwindow_file_register(mwf); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to register mwindow file"); + if (error < 0) + return error; - stats->total = idx->nr_objects; + stats->total = (unsigned int)idx->nr_objects; stats->processed = processed = 0; while (processed < idx->nr_objects) { @@ -325,62 +792,62 @@ int git_indexer_run(git_indexer *idx, git_indexer_stats *stats) struct git_pack_entry *pentry; git_mwindow *w = NULL; int i; - off_t entry_start = off; + git_off_t entry_start = off; void *packed; size_t entry_size; + char fmt[GIT_OID_HEXSZ] = {0}; - entry = git__malloc(sizeof(struct entry)); - memset(entry, 0x0, sizeof(struct entry)); + entry = git__calloc(1, sizeof(*entry)); + GITERR_CHECK_ALLOC(entry); if (off > UINT31_MAX) { entry->offset = UINT32_MAX; entry->offset_long = off; } else { - entry->offset = off; + entry->offset = (uint32_t)off; } error = git_packfile_unpack(&obj, idx->pack, &off); - if (error < GIT_SUCCESS) { - error = git__rethrow(error, "Failed to unpack object"); + if (error < 0) goto cleanup; - } /* FIXME: Parse the object instead of hashing it */ error = git_odb__hashobj(&oid, &obj); - if (error < GIT_SUCCESS) { - error = git__rethrow(error, "Failed to hash object"); + if (error < 0) { + giterr_set(GITERR_INVALID, "Failed to hash object"); goto cleanup; } pentry = git__malloc(sizeof(struct git_pack_entry)); if (pentry == NULL) { - error = GIT_ENOMEM; + error = -1; goto cleanup; } + git_oid_cpy(&pentry->sha1, &oid); pentry->offset = entry_start; + git_oid_fmt(fmt, &oid); + printf("adding %s to cache\n", fmt); error = git_vector_insert(&idx->pack->cache, pentry); - if (error < GIT_SUCCESS) + if (error < 0) goto cleanup; git_oid_cpy(&entry->oid, &oid); entry->crc = crc32(0L, Z_NULL, 0); - entry_size = off - entry_start; + entry_size = (size_t)(off - entry_start); packed = git_mwindow_open(mwf, &w, entry_start, entry_size, &left); if (packed == NULL) { - error = git__rethrow(error, "Failed to open window to read packed data"); + error = -1; goto cleanup; } - entry->crc = htonl(crc32(entry->crc, packed, entry_size)); + entry->crc = htonl(crc32(entry->crc, packed, (uInt)entry_size)); git_mwindow_close(&w); /* Add the object to the list */ error = git_vector_insert(&idx->objects, entry); - if (error < GIT_SUCCESS) { - error = git__rethrow(error, "Failed to add entry to list"); + if (error < 0) goto cleanup; - } for (i = oid.id[0]; i < 256; ++i) { idx->fanout[i]++; diff --git a/src/iterator.c b/src/iterator.c index c026c3c09..3a3be1755 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -9,6 +9,7 @@ #include "tree.h" #include "ignore.h" #include "buffer.h" +#include "git2/submodule.h" typedef struct tree_iterator_frame tree_iterator_frame; struct tree_iterator_frame { @@ -34,25 +35,24 @@ static const git_tree_entry *tree_iterator__tree_entry(tree_iterator *ti) static int tree_iterator__current( git_iterator *self, const git_index_entry **entry) { - int error; tree_iterator *ti = (tree_iterator *)self; const git_tree_entry *te = tree_iterator__tree_entry(ti); *entry = NULL; if (te == NULL) - return GIT_SUCCESS; + return 0; ti->entry.mode = te->attr; git_oid_cpy(&ti->entry.oid, &te->oid); - error = git_buf_joinpath(&ti->path, ti->path.ptr, te->filename); - if (error < GIT_SUCCESS) - return error; + if (git_buf_joinpath(&ti->path, ti->path.ptr, te->filename) < 0) + return -1; + ti->entry.path = ti->path.ptr; *entry = &ti->entry; - return GIT_SUCCESS; + return 0; } static int tree_iterator__at_end(git_iterator *self) @@ -63,7 +63,8 @@ static int tree_iterator__at_end(git_iterator *self) static tree_iterator_frame *tree_iterator__alloc_frame(git_tree *tree) { tree_iterator_frame *tf = git__calloc(1, sizeof(tree_iterator_frame)); - tf->tree = tree; + if (tf != NULL) + tf->tree = tree; return tf; } @@ -75,24 +76,22 @@ static int tree_iterator__expand_tree(tree_iterator *ti) tree_iterator_frame *tf; while (te != NULL && entry_is_tree(te)) { - error = git_tree_lookup(&subtree, ti->repo, &te->oid); - if (error != GIT_SUCCESS) + if ((error = git_tree_lookup(&subtree, ti->repo, &te->oid)) < 0) return error; if ((tf = tree_iterator__alloc_frame(subtree)) == NULL) - return GIT_ENOMEM; + return -1; tf->next = ti->stack; ti->stack = tf; - error = git_buf_joinpath(&ti->path, ti->path.ptr, te->filename); - if (error < GIT_SUCCESS) - return error; + if (git_buf_joinpath(&ti->path, ti->path.ptr, te->filename) < 0) + return -1; te = tree_iterator__tree_entry(ti); } - return GIT_SUCCESS; + return 0; } static void tree_iterator__pop_frame(tree_iterator *ti) @@ -107,7 +106,7 @@ static void tree_iterator__pop_frame(tree_iterator *ti) static int tree_iterator__advance( git_iterator *self, const git_index_entry **entry) { - int error = GIT_SUCCESS; + int error = 0; tree_iterator *ti = (tree_iterator *)self; const git_tree_entry *te = NULL; @@ -123,13 +122,12 @@ static int tree_iterator__advance( break; tree_iterator__pop_frame(ti); - git_buf_rtruncate_at_char(&ti->path, '/'); } if (te && entry_is_tree(te)) error = tree_iterator__expand_tree(ti); - if (error == GIT_SUCCESS && entry != NULL) + if (!error && entry != NULL) error = tree_iterator__current(self, entry); return error; @@ -158,8 +156,7 @@ int git_iterator_for_tree( { int error; tree_iterator *ti = git__calloc(1, sizeof(tree_iterator)); - if (!ti) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(ti); ti->base.type = GIT_ITERATOR_TREE; ti->base.current = tree_iterator__current; @@ -170,11 +167,10 @@ int git_iterator_for_tree( ti->repo = repo; ti->stack = tree_iterator__alloc_frame(tree); - if ((error = tree_iterator__expand_tree(ti)) < GIT_SUCCESS) + if ((error = tree_iterator__expand_tree(ti)) < 0) git_iterator_free((git_iterator *)ti); else *iter = (git_iterator *)ti; - return error; } @@ -190,7 +186,7 @@ static int index_iterator__current( { index_iterator *ii = (index_iterator *)self; *entry = git_index_get(ii->index, ii->current); - return GIT_SUCCESS; + return 0; } static int index_iterator__at_end(git_iterator *self) @@ -207,14 +203,14 @@ static int index_iterator__advance( ii->current++; if (entry) *entry = git_index_get(ii->index, ii->current); - return GIT_SUCCESS; + return 0; } static int index_iterator__reset(git_iterator *self) { index_iterator *ii = (index_iterator *)self; ii->current = 0; - return GIT_SUCCESS; + return 0; } static void index_iterator__free(git_iterator *self) @@ -228,8 +224,7 @@ int git_iterator_for_index(git_repository *repo, git_iterator **iter) { int error; index_iterator *ii = git__calloc(1, sizeof(index_iterator)); - if (!ii) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(ii); ii->base.type = GIT_ITERATOR_INDEX; ii->base.current = index_iterator__current; @@ -239,7 +234,7 @@ int git_iterator_for_index(git_repository *repo, git_iterator **iter) ii->base.free = index_iterator__free; ii->current = 0; - if ((error = git_repository_index(&ii->index, repo)) < GIT_SUCCESS) + if ((error = git_repository_index(&ii->index, repo)) < 0) git__free(ii); else *iter = (git_iterator *)ii; @@ -269,8 +264,8 @@ static workdir_iterator_frame *workdir_iterator__alloc_frame(void) { workdir_iterator_frame *wf = git__calloc(1, sizeof(workdir_iterator_frame)); if (wf == NULL) - return wf; - if (git_vector_init(&wf->entries, 0, git_path_with_stat_cmp) != GIT_SUCCESS) { + return NULL; + if (git_vector_init(&wf->entries, 0, git_path_with_stat_cmp) != 0) { git__free(wf); return NULL; } @@ -294,11 +289,10 @@ static int workdir_iterator__expand_dir(workdir_iterator *wi) { int error; workdir_iterator_frame *wf = workdir_iterator__alloc_frame(); - if (wf == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(wf); error = git_path_dirload_with_stat(wi->path.ptr, wi->root_len, &wf->entries); - if (error < GIT_SUCCESS || wf->entries.length == 0) { + if (error < 0 || wf->entries.length == 0) { workdir_iterator__free_frame(wf); return GIT_ENOTFOUND; } @@ -309,7 +303,7 @@ static int workdir_iterator__expand_dir(workdir_iterator *wi) /* only push new ignores if this is not top level directory */ if (wi->stack->next != NULL) { - int slash_pos = git_buf_rfind_next(&wi->path, '/'); + ssize_t slash_pos = git_buf_rfind_next(&wi->path, '/'); (void)git_ignore__push_dir(&wi->ignores, &wi->path.ptr[slash_pos + 1]); } @@ -321,7 +315,7 @@ static int workdir_iterator__current( { workdir_iterator *wi = (workdir_iterator *)self; *entry = (wi->entry.path == NULL) ? NULL : &wi->entry; - return GIT_SUCCESS; + return 0; } static int workdir_iterator__at_end(git_iterator *self) @@ -341,7 +335,7 @@ static int workdir_iterator__advance( *entry = NULL; if (wi->entry.path == NULL) - return GIT_SUCCESS; + return 0; while ((wf = wi->stack) != NULL) { next = git_vector_get(&wf->entries, ++wf->index); @@ -359,13 +353,13 @@ static int workdir_iterator__advance( if (wi->stack == NULL) { memset(&wi->entry, 0, sizeof(wi->entry)); - return GIT_SUCCESS; + return 0; } } error = workdir_iterator__update_entry(wi); - if (error == GIT_SUCCESS && entry != NULL) + if (!error && entry != NULL) error = workdir_iterator__current(self, entry); return error; @@ -382,7 +376,7 @@ static int workdir_iterator__reset(git_iterator *self) } if (wi->stack) wi->stack->index = 0; - return GIT_SUCCESS; + return 0; } static void workdir_iterator__free(git_iterator *self) @@ -401,13 +395,11 @@ static void workdir_iterator__free(git_iterator *self) static int workdir_iterator__update_entry(workdir_iterator *wi) { - int error; git_path_with_stat *ps = git_vector_get(&wi->stack->entries, wi->stack->index); git_buf_truncate(&wi->path, wi->root_len); - error = git_buf_put(&wi->path, ps->path, ps->path_len); - if (error < GIT_SUCCESS) - return error; + if (git_buf_put(&wi->path, ps->path, ps->path_len) < 0) + return -1; memset(&wi->entry, 0, sizeof(wi->entry)); wi->entry.path = ps->path; @@ -419,39 +411,48 @@ static int workdir_iterator__update_entry(workdir_iterator *wi) /* if there is an error processing the entry, treat as ignored */ wi->is_ignored = 1; - /* TODO: remove shared code for struct stat conversion with index.c */ - wi->entry.ctime.seconds = (git_time_t)ps->st.st_ctime; - wi->entry.mtime.seconds = (git_time_t)ps->st.st_mtime; - wi->entry.dev = ps->st.st_rdev; - wi->entry.ino = ps->st.st_ino; + git_index__init_entry_from_stat(&ps->st, &wi->entry); + + /* need different mode here to keep directories during iteration */ wi->entry.mode = git_futils_canonical_mode(ps->st.st_mode); - wi->entry.uid = ps->st.st_uid; - wi->entry.gid = ps->st.st_gid; - wi->entry.file_size = ps->st.st_size; /* if this is a file type we don't handle, treat as ignored */ if (wi->entry.mode == 0) - return GIT_SUCCESS; + return 0; /* okay, we are far enough along to look up real ignore rule */ - error = git_ignore__lookup(&wi->ignores, wi->entry.path, &wi->is_ignored); - if (error != GIT_SUCCESS) - return GIT_SUCCESS; + if (git_ignore__lookup(&wi->ignores, wi->entry.path, &wi->is_ignored) < 0) + return 0; /* if error, ignore it and ignore file */ /* detect submodules */ - if (S_ISDIR(wi->entry.mode) && - git_path_contains(&wi->path, DOT_GIT) == GIT_SUCCESS) - wi->entry.mode = S_IFGITLINK; + if (S_ISDIR(wi->entry.mode)) { + bool is_submodule = git_path_contains(&wi->path, DOT_GIT); + + /* if there is no .git, still check submodules data */ + if (!is_submodule) { + int res = git_submodule_lookup(NULL, wi->repo, wi->entry.path); + is_submodule = (res == 0); + if (res == GIT_ENOTFOUND) + giterr_clear(); + } - return GIT_SUCCESS; + /* if submodule, mark as GITLINK and remove trailing slash */ + if (is_submodule) { + size_t len = strlen(wi->entry.path); + assert(wi->entry.path[len - 1] == '/'); + wi->entry.path[len - 1] = '\0'; + wi->entry.mode = S_IFGITLINK; + } + } + + return 0; } int git_iterator_for_workdir(git_repository *repo, git_iterator **iter) { int error; workdir_iterator *wi = git__calloc(1, sizeof(workdir_iterator)); - if (!wi) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(wi); wi->base.type = GIT_ITERATOR_WORKDIR; wi->base.current = workdir_iterator__current; @@ -461,19 +462,17 @@ int git_iterator_for_workdir(git_repository *repo, git_iterator **iter) wi->base.free = workdir_iterator__free; wi->repo = repo; - error = git_buf_sets(&wi->path, git_repository_workdir(repo)); - if (error == GIT_SUCCESS) - error = git_path_to_dir(&wi->path); - if (error == GIT_SUCCESS) - error = git_ignore__for_path(repo, "", &wi->ignores); - if (error != GIT_SUCCESS) { + if (git_buf_sets(&wi->path, git_repository_workdir(repo)) < 0 || + git_path_to_dir(&wi->path) < 0 || + git_ignore__for_path(repo, "", &wi->ignores) < 0) + { git__free(wi); - return error; + return -1; } wi->root_len = wi->path.size; - if ((error = workdir_iterator__expand_dir(wi)) < GIT_SUCCESS) + if ((error = workdir_iterator__expand_dir(wi)) < 0) git_iterator_free((git_iterator *)wi); else *iter = (git_iterator *)wi; @@ -487,7 +486,7 @@ int git_iterator_current_tree_entry( { *tree_entry = (iter->type != GIT_ITERATOR_TREE) ? NULL : tree_iterator__tree_entry((tree_iterator *)iter); - return GIT_SUCCESS; + return 0; } int git_iterator_current_is_ignored(git_iterator *iter) @@ -502,12 +501,14 @@ int git_iterator_advance_into_directory( workdir_iterator *wi = (workdir_iterator *)iter; if (iter->type == GIT_ITERATOR_WORKDIR && - wi->entry.path && S_ISDIR(wi->entry.mode)) + wi->entry.path && + S_ISDIR(wi->entry.mode) && + !S_ISGITLINK(wi->entry.mode)) { - if (workdir_iterator__expand_dir(wi) < GIT_SUCCESS) + if (workdir_iterator__expand_dir(wi) < 0) /* if error loading or if empty, skip the directory. */ return workdir_iterator__advance(iter, entry); } - return entry ? git_iterator_current(iter, entry) : GIT_SUCCESS; + return entry ? git_iterator_current(iter, entry) : 0; } diff --git a/src/khash.h b/src/khash.h new file mode 100644 index 000000000..bd67fe1f7 --- /dev/null +++ b/src/khash.h @@ -0,0 +1,608 @@ +/* The MIT License + + Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk> + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +/* + An example: + +#include "khash.h" +KHASH_MAP_INIT_INT(32, char) +int main() { + int ret, is_missing; + khiter_t k; + khash_t(32) *h = kh_init(32); + k = kh_put(32, h, 5, &ret); + kh_value(h, k) = 10; + k = kh_get(32, h, 10); + is_missing = (k == kh_end(h)); + k = kh_get(32, h, 5); + kh_del(32, h, k); + for (k = kh_begin(h); k != kh_end(h); ++k) + if (kh_exist(h, k)) kh_value(h, k) = 1; + kh_destroy(32, h); + return 0; +} +*/ + +/* + 2011-12-29 (0.2.7): + + * Minor code clean up; no actual effect. + + 2011-09-16 (0.2.6): + + * The capacity is a power of 2. This seems to dramatically improve the + speed for simple keys. Thank Zilong Tan for the suggestion. Reference: + + - http://code.google.com/p/ulib/ + - http://nothings.org/computer/judy/ + + * Allow to optionally use linear probing which usually has better + performance for random input. Double hashing is still the default as it + is more robust to certain non-random input. + + * Added Wang's integer hash function (not used by default). This hash + function is more robust to certain non-random input. + + 2011-02-14 (0.2.5): + + * Allow to declare global functions. + + 2009-09-26 (0.2.4): + + * Improve portability + + 2008-09-19 (0.2.3): + + * Corrected the example + * Improved interfaces + + 2008-09-11 (0.2.2): + + * Improved speed a little in kh_put() + + 2008-09-10 (0.2.1): + + * Added kh_clear() + * Fixed a compiling error + + 2008-09-02 (0.2.0): + + * Changed to token concatenation which increases flexibility. + + 2008-08-31 (0.1.2): + + * Fixed a bug in kh_get(), which has not been tested previously. + + 2008-08-31 (0.1.1): + + * Added destructor +*/ + + +#ifndef __AC_KHASH_H +#define __AC_KHASH_H + +/*! + @header + + Generic hash table library. + */ + +#define AC_VERSION_KHASH_H "0.2.6" + +#include <stdlib.h> +#include <string.h> +#include <limits.h> + +/* compipler specific configuration */ + +#if UINT_MAX == 0xffffffffu +typedef unsigned int khint32_t; +#elif ULONG_MAX == 0xffffffffu +typedef unsigned long khint32_t; +#endif + +#if ULONG_MAX == ULLONG_MAX +typedef unsigned long khint64_t; +#else +typedef unsigned long long khint64_t; +#endif + +#ifdef _MSC_VER +#define inline __inline +#endif + +typedef khint32_t khint_t; +typedef khint_t khiter_t; + +#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2) +#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1) +#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3) +#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1))) +#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1))) +#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1))) +#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1)) + +#ifdef KHASH_LINEAR +#define __ac_inc(k, m) 1 +#else +#define __ac_inc(k, m) (((k)>>3 ^ (k)<<3) | 1) & (m) +#endif + +#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4) + +#ifndef kroundup32 +#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) +#endif + +#ifndef kcalloc +#define kcalloc(N,Z) calloc(N,Z) +#endif +#ifndef kmalloc +#define kmalloc(Z) malloc(Z) +#endif +#ifndef krealloc +#define krealloc(P,Z) realloc(P,Z) +#endif +#ifndef kfree +#define kfree(P) free(P) +#endif + +static const double __ac_HASH_UPPER = 0.77; + +#define __KHASH_TYPE(name, khkey_t, khval_t) \ + typedef struct { \ + khint_t n_buckets, size, n_occupied, upper_bound; \ + khint32_t *flags; \ + khkey_t *keys; \ + khval_t *vals; \ + } kh_##name##_t; + +#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \ + extern kh_##name##_t *kh_init_##name(void); \ + extern void kh_destroy_##name(kh_##name##_t *h); \ + extern void kh_clear_##name(kh_##name##_t *h); \ + extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \ + extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \ + extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \ + extern void kh_del_##name(kh_##name##_t *h, khint_t x); + +#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + SCOPE kh_##name##_t *kh_init_##name(void) { \ + return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \ + } \ + SCOPE void kh_destroy_##name(kh_##name##_t *h) \ + { \ + if (h) { \ + kfree((void *)h->keys); kfree(h->flags); \ + kfree((void *)h->vals); \ + kfree(h); \ + } \ + } \ + SCOPE void kh_clear_##name(kh_##name##_t *h) \ + { \ + if (h && h->flags) { \ + memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \ + h->size = h->n_occupied = 0; \ + } \ + } \ + SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \ + { \ + if (h->n_buckets) { \ + khint_t inc, k, i, last, mask; \ + mask = h->n_buckets - 1; \ + k = __hash_func(key); i = k & mask; \ + inc = __ac_inc(k, mask); last = i; /* inc==1 for linear probing */ \ + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ + i = (i + inc) & mask; \ + if (i == last) return h->n_buckets; \ + } \ + return __ac_iseither(h->flags, i)? h->n_buckets : i; \ + } else return 0; \ + } \ + SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ + { /* This function uses 0.25*n_bucktes bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \ + khint32_t *new_flags = 0; \ + khint_t j = 1; \ + { \ + kroundup32(new_n_buckets); \ + if (new_n_buckets < 4) new_n_buckets = 4; \ + if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \ + else { /* hash table size to be changed (shrink or expand); rehash */ \ + new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ + if (!new_flags) return -1; \ + memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ + if (h->n_buckets < new_n_buckets) { /* expand */ \ + khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ + if (!new_keys) return -1; \ + h->keys = new_keys; \ + if (kh_is_map) { \ + khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ + if (!new_vals) return -1; \ + h->vals = new_vals; \ + } \ + } /* otherwise shrink */ \ + } \ + } \ + if (j) { /* rehashing is needed */ \ + for (j = 0; j != h->n_buckets; ++j) { \ + if (__ac_iseither(h->flags, j) == 0) { \ + khkey_t key = h->keys[j]; \ + khval_t val; \ + khint_t new_mask; \ + new_mask = new_n_buckets - 1; \ + if (kh_is_map) val = h->vals[j]; \ + __ac_set_isdel_true(h->flags, j); \ + while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \ + khint_t inc, k, i; \ + k = __hash_func(key); \ + i = k & new_mask; \ + inc = __ac_inc(k, new_mask); \ + while (!__ac_isempty(new_flags, i)) i = (i + inc) & new_mask; \ + __ac_set_isempty_false(new_flags, i); \ + if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \ + { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ + if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \ + __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \ + } else { /* write the element and jump out of the loop */ \ + h->keys[i] = key; \ + if (kh_is_map) h->vals[i] = val; \ + break; \ + } \ + } \ + } \ + } \ + if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \ + h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ + if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ + } \ + kfree(h->flags); /* free the working space */ \ + h->flags = new_flags; \ + h->n_buckets = new_n_buckets; \ + h->n_occupied = h->size; \ + h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \ + } \ + return 0; \ + } \ + SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \ + { \ + khint_t x; \ + if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \ + if (h->n_buckets > (h->size<<1)) { \ + if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \ + *ret = -1; return h->n_buckets; \ + } \ + } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \ + *ret = -1; return h->n_buckets; \ + } \ + } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ + { \ + khint_t inc, k, i, site, last, mask = h->n_buckets - 1; \ + x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \ + if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \ + else { \ + inc = __ac_inc(k, mask); last = i; \ + while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ + if (__ac_isdel(h->flags, i)) site = i; \ + i = (i + inc) & mask; \ + if (i == last) { x = site; break; } \ + } \ + if (x == h->n_buckets) { \ + if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \ + else x = i; \ + } \ + } \ + } \ + if (__ac_isempty(h->flags, x)) { /* not present at all */ \ + h->keys[x] = key; \ + __ac_set_isboth_false(h->flags, x); \ + ++h->size; ++h->n_occupied; \ + *ret = 1; \ + } else if (__ac_isdel(h->flags, x)) { /* deleted */ \ + h->keys[x] = key; \ + __ac_set_isboth_false(h->flags, x); \ + ++h->size; \ + *ret = 2; \ + } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \ + return x; \ + } \ + SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \ + { \ + if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \ + __ac_set_isdel_true(h->flags, x); \ + --h->size; \ + } \ + } + +#define KHASH_DECLARE(name, khkey_t, khval_t) \ + __KHASH_TYPE(name, khkey_t, khval_t) \ + __KHASH_PROTOTYPES(name, khkey_t, khval_t) + +#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + __KHASH_TYPE(name, khkey_t, khval_t) \ + __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) + +#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ + KHASH_INIT2(name, static inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) + +/* --- BEGIN OF HASH FUNCTIONS --- */ + +/*! @function + @abstract Integer hash function + @param key The integer [khint32_t] + @return The hash value [khint_t] + */ +#define kh_int_hash_func(key) (khint32_t)(key) +/*! @function + @abstract Integer comparison function + */ +#define kh_int_hash_equal(a, b) ((a) == (b)) +/*! @function + @abstract 64-bit integer hash function + @param key The integer [khint64_t] + @return The hash value [khint_t] + */ +#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11) +/*! @function + @abstract 64-bit integer comparison function + */ +#define kh_int64_hash_equal(a, b) ((a) == (b)) +/*! @function + @abstract const char* hash function + @param s Pointer to a null terminated string + @return The hash value + */ +static inline khint_t __ac_X31_hash_string(const char *s) +{ + khint_t h = (khint_t)*s; + if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s; + return h; +} +/*! @function + @abstract Another interface to const char* hash function + @param key Pointer to a null terminated string [const char*] + @return The hash value [khint_t] + */ +#define kh_str_hash_func(key) __ac_X31_hash_string(key) +/*! @function + @abstract Const char* comparison function + */ +#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) + +static inline khint_t __ac_Wang_hash(khint_t key) +{ + key += ~(key << 15); + key ^= (key >> 10); + key += (key << 3); + key ^= (key >> 6); + key += ~(key << 11); + key ^= (key >> 16); + return key; +} +#define kh_int_hash_func2(k) __ac_Wang_hash((khint_t)key) + +/* --- END OF HASH FUNCTIONS --- */ + +/* Other convenient macros... */ + +/*! + @abstract Type of the hash table. + @param name Name of the hash table [symbol] + */ +#define khash_t(name) kh_##name##_t + +/*! @function + @abstract Initiate a hash table. + @param name Name of the hash table [symbol] + @return Pointer to the hash table [khash_t(name)*] + */ +#define kh_init(name) kh_init_##name() + +/*! @function + @abstract Destroy a hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + */ +#define kh_destroy(name, h) kh_destroy_##name(h) + +/*! @function + @abstract Reset a hash table without deallocating memory. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + */ +#define kh_clear(name, h) kh_clear_##name(h) + +/*! @function + @abstract Resize a hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param s New size [khint_t] + */ +#define kh_resize(name, h, s) kh_resize_##name(h, s) + +/*! @function + @abstract Insert a key to the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Key [type of keys] + @param r Extra return code: 0 if the key is present in the hash table; + 1 if the bucket is empty (never used); 2 if the element in + the bucket has been deleted [int*] + @return Iterator to the inserted element [khint_t] + */ +#define kh_put(name, h, k, r) kh_put_##name(h, k, r) + +/*! @function + @abstract Retrieve a key from the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Key [type of keys] + @return Iterator to the found element, or kh_end(h) is the element is absent [khint_t] + */ +#define kh_get(name, h, k) kh_get_##name(h, k) + +/*! @function + @abstract Remove a key from the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Iterator to the element to be deleted [khint_t] + */ +#define kh_del(name, h, k) kh_del_##name(h, k) + +/*! @function + @abstract Test whether a bucket contains data. + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return 1 if containing data; 0 otherwise [int] + */ +#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x))) + +/*! @function + @abstract Get key given an iterator + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return Key [type of keys] + */ +#define kh_key(h, x) ((h)->keys[x]) + +/*! @function + @abstract Get value given an iterator + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return Value [type of values] + @discussion For hash sets, calling this results in segfault. + */ +#define kh_val(h, x) ((h)->vals[x]) + +/*! @function + @abstract Alias of kh_val() + */ +#define kh_value(h, x) ((h)->vals[x]) + +/*! @function + @abstract Get the start iterator + @param h Pointer to the hash table [khash_t(name)*] + @return The start iterator [khint_t] + */ +#define kh_begin(h) (khint_t)(0) + +/*! @function + @abstract Get the end iterator + @param h Pointer to the hash table [khash_t(name)*] + @return The end iterator [khint_t] + */ +#define kh_end(h) ((h)->n_buckets) + +/*! @function + @abstract Get the number of elements in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @return Number of elements in the hash table [khint_t] + */ +#define kh_size(h) ((h)->size) + +/*! @function + @abstract Get the number of buckets in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @return Number of buckets in the hash table [khint_t] + */ +#define kh_n_buckets(h) ((h)->n_buckets) + +/*! @function + @abstract Iterate over the entries in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @param kvar Variable to which key will be assigned + @param vvar Variable to which value will be assigned + @param code Block of code to execute + */ +#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \ + for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ + if (!kh_exist(h,__i)) continue; \ + (kvar) = kh_key(h,__i); \ + (vvar) = kh_val(h,__i); \ + code; \ + } } + +/*! @function + @abstract Iterate over the values in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @param vvar Variable to which value will be assigned + @param code Block of code to execute + */ +#define kh_foreach_value(h, vvar, code) { khint_t __i; \ + for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ + if (!kh_exist(h,__i)) continue; \ + (vvar) = kh_val(h,__i); \ + code; \ + } } + +/* More conenient interfaces */ + +/*! @function + @abstract Instantiate a hash set containing integer keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_INT(name) \ + KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing integer keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_INT(name, khval_t) \ + KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing 64-bit integer keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_INT64(name) \ + KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing 64-bit integer keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_INT64(name, khval_t) \ + KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal) + +typedef const char *kh_cstr_t; +/*! @function + @abstract Instantiate a hash map containing const char* keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_STR(name) \ + KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing const char* keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_STR(name, khval_t) \ + KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal) + +#endif /* __AC_KHASH_H */ @@ -31,6 +31,11 @@ typedef struct { /* memory mapped buffer */ #endif } git_map; +#define GIT_MMAP_VALIDATE(out, len, prot, flags) do { \ + assert(out != NULL && len > 0); \ + assert((prot & GIT_PROT_WRITE) || (prot & GIT_PROT_READ)); \ + assert((flags & GIT_MAP_FIXED) == 0); } while (0) + extern int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset); extern int p_munmap(git_map *map); diff --git a/src/mwindow.c b/src/mwindow.c index f657d9d34..fa5549021 100644 --- a/src/mwindow.c +++ b/src/mwindow.c @@ -89,6 +89,7 @@ void git_mwindow_scan_lru( { git_mwindow *w, *w_l; + puts("LRU"); for (w_l = NULL, w = mwf->windows; w; w = w->next) { if (!w->inuse_cnt) { /* @@ -115,7 +116,7 @@ static int git_mwindow_close_lru(git_mwindow_file *mwf) unsigned int i; git_mwindow *lru_w = NULL, *lru_l = NULL, **list = &mwf->windows; - /* FIMXE: Does this give us any advantage? */ + /* FIXME: Does this give us any advantage? */ if(mwf->windows) git_mwindow_scan_lru(mwf, &lru_w, &lru_l); @@ -127,22 +128,23 @@ static int git_mwindow_close_lru(git_mwindow_file *mwf) list = &cur->windows; } - if (lru_w) { - ctl->mapped -= lru_w->window_map.len; - git_futils_mmap_free(&lru_w->window_map); + if (!lru_w) { + giterr_set(GITERR_OS, "Failed to close memory window. Couldn't find LRU"); + return -1; + } - if (lru_l) - lru_l->next = lru_w->next; - else - *list = lru_w->next; + ctl->mapped -= lru_w->window_map.len; + git_futils_mmap_free(&lru_w->window_map); - git__free(lru_w); - ctl->open_windows--; + if (lru_l) + lru_l->next = lru_w->next; + else + *list = lru_w->next; - return GIT_SUCCESS; - } + git__free(lru_w); + ctl->open_windows--; - return git__throw(GIT_ERROR, "Failed to close memory window. Couln't find LRU"); + return 0; } static git_mwindow *new_window( @@ -158,7 +160,7 @@ static git_mwindow *new_window( w = git__malloc(sizeof(*w)); if (w == NULL) - return w; + return NULL; memset(w, 0x0, sizeof(*w)); w->offset = (offset / walign) * walign; @@ -170,7 +172,7 @@ static git_mwindow *new_window( ctl->mapped += (size_t)len; while (_mw_options.mapped_limit < ctl->mapped && - git_mwindow_close_lru(mwf) == GIT_SUCCESS) /* nop */; + git_mwindow_close_lru(mwf) == 0) /* nop */; /* * We treat _mw_options.mapped_limit as a soft limit. If we can't find a @@ -178,8 +180,10 @@ static git_mwindow *new_window( * window. */ - if (git_futils_mmap_ro(&w->window_map, fd, w->offset, (size_t)len) < GIT_SUCCESS) - goto cleanup; + if (git_futils_mmap_ro(&w->window_map, fd, w->offset, (size_t)len) < 0) { + git__free(w); + return NULL; + } ctl->mmap_calls++; ctl->open_windows++; @@ -191,10 +195,6 @@ static git_mwindow *new_window( ctl->peak_open_windows = ctl->open_windows; return w; - -cleanup: - git__free(w); - return NULL; } /* @@ -205,14 +205,13 @@ unsigned char *git_mwindow_open( git_mwindow_file *mwf, git_mwindow **cursor, git_off_t offset, - int extra, + size_t extra, unsigned int *left) { git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl; git_mwindow *w = *cursor; - if (!w || !(git_mwindow_contains(w, offset) && - git_mwindow_contains(w, offset + extra))) { + if (!w || !(git_mwindow_contains(w, offset) && git_mwindow_contains(w, offset + extra))) { if (w) { w->inuse_cnt--; } @@ -221,6 +220,7 @@ unsigned char *git_mwindow_open( if (git_mwindow_contains(w, offset) && git_mwindow_contains(w, offset + extra)) break; + } } /* @@ -248,17 +248,17 @@ unsigned char *git_mwindow_open( if (left) *left = (unsigned int)(w->window_map.len - offset); + fflush(stdout); return (unsigned char *) w->window_map.data + offset; } int git_mwindow_file_register(git_mwindow_file *mwf) { git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl; - int error; if (ctl->windowfiles.length == 0 && - (error = git_vector_init(&ctl->windowfiles, 8, NULL)) < GIT_SUCCESS) - return error; + git_vector_init(&ctl->windowfiles, 8, NULL) < 0) + return -1; return git_vector_insert(&ctl->windowfiles, mwf); } diff --git a/src/mwindow.h b/src/mwindow.h index 94bfb5d61..058027251 100644 --- a/src/mwindow.h +++ b/src/mwindow.h @@ -15,8 +15,8 @@ typedef struct git_mwindow { struct git_mwindow *next; git_map window_map; git_off_t offset; - unsigned int last_used; - unsigned int inuse_cnt; + size_t last_used; + size_t inuse_cnt; } git_mwindow; typedef struct git_mwindow_file { @@ -37,7 +37,7 @@ typedef struct git_mwindow_ctl { int git_mwindow_contains(git_mwindow *win, git_off_t offset); void git_mwindow_free_all(git_mwindow_file *mwf); -unsigned char *git_mwindow_open(git_mwindow_file *mwf, git_mwindow **cursor, git_off_t offset, int extra, unsigned int *left); +unsigned char *git_mwindow_open(git_mwindow_file *mwf, git_mwindow **cursor, git_off_t offset, size_t extra, unsigned int *left); void git_mwindow_scan_lru(git_mwindow_file *mwf, git_mwindow **lru_w, git_mwindow **lru_l); int git_mwindow_file_register(git_mwindow_file *mwf); void git_mwindow_close(git_mwindow **w_cursor); diff --git a/src/netops.c b/src/netops.c index 4b307af45..e2fec0b48 100644 --- a/src/netops.c +++ b/src/netops.c @@ -25,8 +25,28 @@ #include "common.h" #include "netops.h" #include "posix.h" +#include "buffer.h" -void gitno_buffer_setup(gitno_buffer *buf, char *data, unsigned int len, int fd) +#ifdef GIT_WIN32 +static void net_set_error(const char *str) +{ + int size, error = WSAGetLastError(); + LPSTR err_str = NULL; + + size = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + 0, error, 0, (LPSTR)&err_str, 0, 0); + + giterr_set(GITERR_NET, "%s: $s", str, err_str); + LocalFree(err_str); +} +#else +static void net_set_error(const char *str) +{ + giterr_set(GITERR_NET, "%s: %s", str, strerror(errno)); +} +#endif + +void gitno_buffer_setup(gitno_buffer *buf, char *data, unsigned int len, GIT_SOCKET fd) { memset(buf, 0x0, sizeof(gitno_buffer)); memset(data, 0x0, len); @@ -40,14 +60,13 @@ int gitno_recv(gitno_buffer *buf) { int ret; - ret = recv(buf->fd, buf->data + buf->offset, buf->len - buf->offset, 0); - if (ret < 0) - return git__throw(GIT_EOSERR, "Failed to receive data: %s", strerror(errno)); - if (ret == 0) /* Orderly shutdown, so exit */ - return GIT_SUCCESS; + ret = p_recv(buf->fd, buf->data + buf->offset, buf->len - buf->offset, 0); + if (ret < 0) { + net_set_error("Error receiving socket data"); + return -1; + } buf->offset += ret; - return ret; } @@ -74,52 +93,43 @@ void gitno_consume_n(gitno_buffer *buf, size_t cons) buf->offset -= cons; } -int gitno_connect(const char *host, const char *port) +GIT_SOCKET gitno_connect(const char *host, const char *port) { - struct addrinfo *info, *p; + struct addrinfo *info = NULL, *p; struct addrinfo hints; - int ret, error = GIT_SUCCESS; - GIT_SOCKET s; + int ret; + GIT_SOCKET s = INVALID_SOCKET; memset(&hints, 0x0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; - ret = getaddrinfo(host, port, &hints, &info); - if (ret != 0) { - error = GIT_EOSERR; - info = NULL; - goto cleanup; + if ((ret = getaddrinfo(host, port, &hints, &info)) < 0) { + giterr_set(GITERR_NET, "Failed to resolve address for %s: %s", host, gai_strerror(ret)); + return INVALID_SOCKET; } for (p = info; p != NULL; p = p->ai_next) { s = socket(p->ai_family, p->ai_socktype, p->ai_protocol); -#ifdef GIT_WIN32 if (s == INVALID_SOCKET) { -#else - if (s < 0) { -#endif - error = GIT_EOSERR; - goto cleanup; + net_set_error("error creating socket"); + break; } - ret = connect(s, p->ai_addr, p->ai_addrlen); - /* If we can't connect, try the next one */ - if (ret < 0) { - continue; - } + if (connect(s, p->ai_addr, (socklen_t)p->ai_addrlen) == 0) + break; - /* Return the socket */ - error = s; - goto cleanup; + /* If we can't connect, try the next one */ + gitno_close(s); + s = INVALID_SOCKET; } /* Oops, we couldn't connect to any address */ - error = git__throw(GIT_EOSERR, "Failed to connect: %s", strerror(errno)); + if (s == INVALID_SOCKET && p == NULL) + giterr_set(GITERR_OS, "Failed to connect to %s", host); -cleanup: freeaddrinfo(info); - return error; + return s; } int gitno_send(GIT_SOCKET s, const char *msg, size_t len, int flags) @@ -130,14 +140,16 @@ int gitno_send(GIT_SOCKET s, const char *msg, size_t len, int flags) while (off < len) { errno = 0; - ret = send(s, msg + off, len - off, flags); - if (ret < 0) - return git__throw(GIT_EOSERR, "Error sending data: %s", strerror(errno)); + ret = p_send(s, msg + off, len - off, flags); + if (ret < 0) { + net_set_error("Error sending data"); + return -1; + } off += ret; } - return off; + return (int)off; } @@ -165,35 +177,31 @@ int gitno_select_in(gitno_buffer *buf, long int sec, long int usec) FD_SET(buf->fd, &fds); /* The select(2) interface is silly */ - return select(buf->fd + 1, &fds, NULL, NULL, &tv); + return select((int)buf->fd + 1, &fds, NULL, NULL, &tv); } int gitno_extract_host_and_port(char **host, char **port, const char *url, const char *default_port) { char *colon, *slash, *delim; - int error = GIT_SUCCESS; colon = strchr(url, ':'); slash = strchr(url, '/'); - if (slash == NULL) - return git__throw(GIT_EOBJCORRUPTED, "Malformed URL: missing /"); + if (slash == NULL) { + giterr_set(GITERR_NET, "Malformed URL: missing /"); + return -1; + } if (colon == NULL) { *port = git__strdup(default_port); } else { *port = git__strndup(colon + 1, slash - colon - 1); } - if (*port == NULL) - return GIT_ENOMEM;; - + GITERR_CHECK_ALLOC(*port); delim = colon == NULL ? slash : colon; *host = git__strndup(url, delim - url); - if (*host == NULL) { - git__free(*port); - error = GIT_ENOMEM; - } + GITERR_CHECK_ALLOC(*host); - return error; + return 0; } diff --git a/src/netops.h b/src/netops.h index 01ad9714f..f370019ff 100644 --- a/src/netops.h +++ b/src/netops.h @@ -7,11 +7,7 @@ #ifndef INCLUDE_netops_h__ #define INCLUDE_netops_h__ -#ifndef GIT_WIN32 -typedef int GIT_SOCKET; -#else -typedef SOCKET GIT_SOCKET; -#endif +#include "posix.h" typedef struct gitno_buffer { char *data; @@ -20,12 +16,12 @@ typedef struct gitno_buffer { GIT_SOCKET fd; } gitno_buffer; -void gitno_buffer_setup(gitno_buffer *buf, char *data, unsigned int len, int fd); +void gitno_buffer_setup(gitno_buffer *buf, char *data, unsigned int len, GIT_SOCKET fd); int gitno_recv(gitno_buffer *buf); void gitno_consume(gitno_buffer *buf, const char *ptr); void gitno_consume_n(gitno_buffer *buf, size_t cons); -int gitno_connect(const char *host, const char *port); +GIT_SOCKET gitno_connect(const char *host, const char *port); int gitno_send(GIT_SOCKET s, const char *msg, size_t len, int flags); int gitno_close(GIT_SOCKET s); int gitno_send_chunk_size(int s, size_t len); diff --git a/src/notes.c b/src/notes.c index 68554c36f..05c70c643 100644 --- a/src/notes.c +++ b/src/notes.c @@ -21,11 +21,8 @@ static int find_subtree(git_tree **subtree, const git_oid *root, *subtree = NULL; error = git_tree_lookup(&tree, repo, root); - if (error < GIT_SUCCESS) { - if (error == GIT_ENOTFOUND) - return error; /* notes tree doesn't exist yet */ - return git__rethrow(error, "Failed to open notes tree"); - } + if (error < 0) + return error; for (i=0; i<git_tree_entrycount(tree); i++) { entry = git_tree_entry_byindex(tree, i); @@ -56,7 +53,7 @@ static int find_subtree(git_tree **subtree, const git_oid *root, } *subtree = tree; - return GIT_SUCCESS; + return 0; } static int find_blob(git_oid *blob, git_tree *tree, const char *target) @@ -71,7 +68,7 @@ static int find_blob(git_oid *blob, git_tree *tree, const char *target) /* found matching note object - return */ git_oid_cpy(blob, git_tree_entry_id(entry)); - return GIT_SUCCESS; + return 0; } } return GIT_ENOTFOUND; @@ -93,18 +90,17 @@ static int note_write(git_oid *out, git_repository *repo, if (tree_sha) { error = find_subtree(&tree, tree_sha, repo, target, &fanout); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to lookup subtree"); + if (error < 0) + return error; error = find_blob(&oid, tree, target + fanout); - if (error < GIT_SUCCESS && error != GIT_ENOTFOUND) { - git_tree_free(tree); - return git__throw(GIT_ENOTFOUND, "Failed to read subtree %s", target); - } - - if (error == GIT_SUCCESS) { + if (error != GIT_ENOTFOUND) { git_tree_free(tree); - return git__throw(GIT_EEXISTS, "Note for `%s` exists already", target); + if (!error) { + giterr_set(GITERR_REPOSITORY, "Note for '%s' exists already", target); + error = GIT_EEXISTS; + } + return error; } } @@ -113,8 +109,8 @@ static int note_write(git_oid *out, git_repository *repo, error = git_treebuilder_create(&tb, tree); git_tree_free(tree); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to create treebuilder"); + if (error < 0) + return error; if (!tree_sha) /* no notes tree yet - create fanout */ @@ -122,18 +118,18 @@ static int note_write(git_oid *out, git_repository *repo, /* create note object */ error = git_blob_create_frombuffer(&oid, repo, note, strlen(note)); - if (error < GIT_SUCCESS) { + if (error < 0) { git_treebuilder_free(tb); - return git__rethrow(error, "Failed to create note object"); + return error; } error = git_treebuilder_insert(&entry, tb, target + fanout, &oid, 0100644); - if (error < GIT_SUCCESS) { + if (error < 0) { /* libgit2 doesn't support object removal (gc) yet */ /* we leave an orphaned blob object behind - TODO */ git_treebuilder_free(tb); - return git__rethrow(error, "Failed to insert note object"); + return error; } if (out) @@ -142,8 +138,8 @@ static int note_write(git_oid *out, git_repository *repo, error = git_treebuilder_write(&oid, repo, tb); git_treebuilder_free(tb); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to write notes tree"); + if (error < 0) + return 0; if (!tree_sha) { /* create fanout subtree */ @@ -153,27 +149,28 @@ static int note_write(git_oid *out, git_repository *repo, subtree[2] = '\0'; error = git_treebuilder_create(&tb, NULL); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to create treebuilder"); + if (error < 0) + return error; error = git_treebuilder_insert(NULL, tb, subtree, &oid, 0040000); - if (error < GIT_SUCCESS) { + if (error < 0) { git_treebuilder_free(tb); - return git__rethrow(error, "Failed to insert note object"); + return error; } error = git_treebuilder_write(&oid, repo, tb); + git_treebuilder_free(tb); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to write notes tree"); + if (error < 0) + return error; } /* create new notes commit */ error = git_tree_lookup(&tree, repo, &oid); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to open new notes tree"); + if (error < 0) + return error; error = git_commit_create(&oid, repo, notes_ref, author, committer, NULL, GIT_NOTES_DEFAULT_MSG_ADD, @@ -181,10 +178,7 @@ static int note_write(git_oid *out, git_repository *repo, git_tree_free(tree); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to create new notes commit"); - - return GIT_SUCCESS; + return error; } static int note_lookup(git_note **out, git_repository *repo, @@ -197,31 +191,25 @@ static int note_lookup(git_note **out, git_repository *repo, git_note *note; error = find_subtree(&tree, tree_sha, repo, target, &fanout); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to lookup subtree"); + if (error < 0) + return error; error = find_blob(&oid, tree, target + fanout); - if (error < GIT_SUCCESS) { - git_tree_free(tree); - return git__throw(GIT_ENOTFOUND, "No note found for object %s", - target); - } + git_tree_free(tree); + if (error < 0) + return error; error = git_blob_lookup(&blob, repo, &oid); - if (error < GIT_SUCCESS) - return git__throw(GIT_ERROR, "Failed to lookup note object"); + if (error < 0) + return error; note = git__malloc(sizeof(git_note)); - if (note == NULL) { - git_blob_free(blob); - return GIT_ENOMEM; - } + GITERR_CHECK_ALLOC(note); git_oid_cpy(¬e->oid, &oid); note->message = git__strdup(git_blob_rawcontent(blob)); - if (note->message == NULL) - error = GIT_ENOMEM; + GITERR_CHECK_ALLOC(note->message); *out = note; @@ -240,39 +228,30 @@ static int note_remove(git_repository *repo, git_treebuilder *tb; error = find_subtree(&tree, tree_sha, repo, target, &fanout); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to lookup subtree"); + if (error < 0) + return error; error = find_blob(&oid, tree, target + fanout); - if (error < GIT_SUCCESS) { - git_tree_free(tree); - return git__throw(GIT_ENOTFOUND, "No note found for object %s", - target); - } + if (!error) + error = git_treebuilder_create(&tb, tree); - error = git_treebuilder_create(&tb, tree); git_tree_free(tree); - - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to create treebuilder"); + if (error < 0) + return error; error = git_treebuilder_remove(tb, target + fanout); - if (error < GIT_SUCCESS) { - git_treebuilder_free(tb); - return git__rethrow(error, "Failed to remove entry from notes tree"); - } + if (!error) + error = git_treebuilder_write(&oid, repo, tb); - error = git_treebuilder_write(&oid, repo, tb); git_treebuilder_free(tb); - - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to write notes tree"); + if (error < 0) + return error; /* create new notes commit */ error = git_tree_lookup(&tree, repo, &oid); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to open new notes tree"); + if (error < 0) + return error; error = git_commit_create(&oid, repo, notes_ref, author, committer, NULL, GIT_NOTES_DEFAULT_MSG_RM, @@ -280,9 +259,6 @@ static int note_remove(git_repository *repo, git_tree_free(tree); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to create new notes commit"); - return error; } @@ -301,8 +277,8 @@ int git_note_read(git_note **out, git_repository *repo, notes_ref = GIT_NOTES_DEFAULT_REF; error = git_reference_lookup(&ref, repo, notes_ref); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to lookup reference `%s`", notes_ref); + if (error < 0) + return error; assert(git_reference_type(ref) == GIT_REF_OID); @@ -311,27 +287,26 @@ int git_note_read(git_note **out, git_repository *repo, git_reference_free(ref); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to find notes commit object"); + if (error < 0) + return error; sha = git_commit_tree_oid(commit); git_commit_free(commit); target = git_oid_allocfmt(oid); - if (target == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(target); error = note_lookup(out, repo, sha, target); git__free(target); - return error == GIT_SUCCESS ? GIT_SUCCESS : - git__rethrow(error, "Failed to read note"); + return error; } -int git_note_create(git_oid *out, git_repository *repo, - git_signature *author, git_signature *committer, - const char *notes_ref, const git_oid *oid, - const char *note) +int git_note_create( + git_oid *out, git_repository *repo, + git_signature *author, git_signature *committer, + const char *notes_ref, const git_oid *oid, + const char *note) { int error, nparents = 0; char *target; @@ -343,10 +318,10 @@ int git_note_create(git_oid *out, git_repository *repo, notes_ref = GIT_NOTES_DEFAULT_REF; error = git_reference_lookup(&ref, repo, notes_ref); - if (error < GIT_SUCCESS && error != GIT_ENOTFOUND) - return git__rethrow(error, "Failed to lookup reference `%s`", notes_ref); + if (error < 0 && error != GIT_ENOTFOUND) + return error; - if (error == GIT_SUCCESS) { + if (!error) { assert(git_reference_type(ref) == GIT_REF_OID); /* lookup existing notes tree oid */ @@ -355,16 +330,15 @@ int git_note_create(git_oid *out, git_repository *repo, git_reference_free(ref); error = git_commit_lookup(&commit, repo, &sha); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to find notes commit object"); + if (error < 0) + return error; git_oid_cpy(&sha, git_commit_tree_oid(commit)); nparents++; } target = git_oid_allocfmt(oid); - if (target == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(target); error = note_write(out, repo, author, committer, notes_ref, note, nparents ? &sha : NULL, target, @@ -372,8 +346,7 @@ int git_note_create(git_oid *out, git_repository *repo, git__free(target); git_commit_free(commit); - return error == GIT_SUCCESS ? GIT_SUCCESS : - git__rethrow(error, "Failed to write note"); + return error; } int git_note_remove(git_repository *repo, const char *notes_ref, @@ -390,8 +363,8 @@ int git_note_remove(git_repository *repo, const char *notes_ref, notes_ref = GIT_NOTES_DEFAULT_REF; error = git_reference_lookup(&ref, repo, notes_ref); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to lookup reference `%s`", notes_ref); + if (error < 0) + return error; assert(git_reference_type(ref) == GIT_REF_OID); @@ -399,22 +372,20 @@ int git_note_remove(git_repository *repo, const char *notes_ref, git_reference_free(ref); error = git_commit_lookup(&commit, repo, &sha); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to find notes commit object"); + if (error < 0) + return error; git_oid_cpy(&sha, git_commit_tree_oid(commit)); target = git_oid_allocfmt(oid); - if (target == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(target); error = note_remove(repo, author, committer, notes_ref, &sha, target, 1, &commit); git__free(target); git_commit_free(commit); - return error == GIT_SUCCESS ? GIT_SUCCESS : - git__rethrow(error, "Failed to read note"); + return error; } const char * git_note_message(git_note *note) diff --git a/src/object.c b/src/object.c index 043001599..979fb40ca 100644 --- a/src/object.c +++ b/src/object.c @@ -68,7 +68,8 @@ static int create_object(git_object **object_out, git_otype type) break; default: - return git__throw(GIT_EINVALIDTYPE, "The given type is invalid"); + giterr_set(GITERR_INVALID, "The given type is invalid"); + return -1; } object->type = type; @@ -92,8 +93,7 @@ int git_object_lookup_prefix( assert(repo && object_out && id); if (len < GIT_OID_MINPREFIXLEN) - return git__throw(GIT_EAMBIGUOUSOIDPREFIX, - "Failed to lookup object. Prefix length is lower than %d.", GIT_OID_MINPREFIXLEN); + return GIT_EAMBIGUOUS; error = git_repository_odb__weakptr(&odb, repo); if (error < GIT_SUCCESS) @@ -110,13 +110,12 @@ int git_object_lookup_prefix( if (object != NULL) { if (type != GIT_OBJ_ANY && type != object->type) { git_object_free(object); - return git__throw(GIT_EINVALIDTYPE, - "Failed to lookup object. " - "The given type does not match the type on the ODB"); + giterr_set(GITERR_INVALID, "The given type does not match the type in ODB"); + return -1; } *object_out = object; - return GIT_SUCCESS; + return 0; } /* Object was not found in the cache, let's explore the backends. @@ -147,18 +146,19 @@ int git_object_lookup_prefix( error = git_odb_read_prefix(&odb_obj, odb, &short_oid, len); } - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to lookup object"); + if (error < 0) + return -1; if (type != GIT_OBJ_ANY && type != odb_obj->raw.type) { git_odb_object_free(odb_obj); - return git__throw(GIT_EINVALIDTYPE, "Failed to lookup object. The given type does not match the type on the ODB"); + giterr_set(GITERR_INVALID, "The given type does not match the type on the ODB"); + return -1; } type = odb_obj->raw.type; - if ((error = create_object(&object, type)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to lookup object"); + if (create_object(&object, type) < 0) + return -1; /* Initialize parent object */ git_oid_cpy(&object->cached.oid, &odb_obj->cached.oid); @@ -187,13 +187,13 @@ int git_object_lookup_prefix( git_odb_object_free(odb_obj); - if (error < GIT_SUCCESS) { + if (error < 0) { git_object__free(object); - return git__rethrow(error, "Failed to lookup object"); + return -1; } *object_out = git_cache_try_store(&repo->objects, object); - return GIT_SUCCESS; + return 0; } int git_object_lookup(git_object **object_out, git_repository *repo, const git_oid *id, git_otype type) { @@ -32,10 +32,7 @@ static int format_object_header(char *hdr, size_t n, size_t obj_len, git_otype o { const char *type_str = git_object_type2string(obj_type); int len = p_snprintf(hdr, n, "%s %"PRIuZ, type_str, obj_len); - - if (len < 0 || len >= (int)n) - return git__throw(GIT_ERROR, "Cannot format object header. Length is out of bounds"); - + assert(len > 0 && len <= (int)n); return len+1; } @@ -48,13 +45,11 @@ int git_odb__hashobj(git_oid *id, git_rawobj *obj) assert(id && obj); if (!git_object_typeisloose(obj->type)) - return git__throw(GIT_ERROR, "Failed to hash object. Wrong object type"); - + return -1; if (!obj->data && obj->len != 0) - return git__throw(GIT_ERROR, "Failed to hash object. No data given"); + return -1; - if ((hdrlen = format_object_header(header, sizeof(header), obj->len, obj->type)) < 0) - return git__rethrow(hdrlen, "Failed to hash object"); + hdrlen = format_object_header(header, sizeof(header), obj->len, obj->type); vec[0].data = header; vec[0].len = hdrlen; @@ -63,7 +58,7 @@ int git_odb__hashobj(git_oid *id, git_rawobj *obj) git_hash_vec(id, vec, 2); - return GIT_SUCCESS; + return 0; } @@ -120,8 +115,6 @@ int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type) git_hash_ctx *ctx; hdr_len = format_object_header(hdr, sizeof(hdr), size, type); - if (hdr_len < 0) - return git__throw(GIT_ERROR, "Failed to format blob header. Length is out of bounds"); ctx = git_hash_new_ctx(); @@ -132,7 +125,8 @@ int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type) if (read_len < 0) { git_hash_free_ctx(ctx); - return git__throw(GIT_EOSERR, "Error when reading file: %s", strerror(errno)); + giterr_set(GITERR_OS, "Error reading file"); + return -1; } git_hash_update(ctx, buffer, read_len); @@ -142,69 +136,67 @@ int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type) git_hash_final(out, ctx); git_hash_free_ctx(ctx); - return GIT_SUCCESS; + return 0; } int git_odb__hashlink(git_oid *out, const char *path) { struct stat st; - int error; git_off_t size; + int result; - error = p_lstat(path, &st); - if (error < 0) - return git__throw(GIT_EOSERR, "Failed to stat blob. %s", strerror(errno)); + if (git_path_lstat(path, &st) < 0) + return -1; size = st.st_size; - if (!git__is_sizet(size)) - return git__throw(GIT_EOSERR, "File size overflow for 32-bit systems"); + if (!git__is_sizet(size)) { + giterr_set(GITERR_OS, "File size overflow for 32-bit systems"); + return -1; + } if (S_ISLNK(st.st_mode)) { char *link_data; ssize_t read_len; link_data = git__malloc((size_t)size); - if (link_data == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(link_data); read_len = p_readlink(path, link_data, (size_t)(size + 1)); - if (read_len != (ssize_t)size) - return git__throw(GIT_EOSERR, "Failed to read symlink data"); + if (read_len != (ssize_t)size) { + giterr_set(GITERR_OS, "Failed to read symlink data for '%s'", path); + return -1; + } - error = git_odb_hash(out, link_data, (size_t)size, GIT_OBJ_BLOB); - free(link_data); + result = git_odb_hash(out, link_data, (size_t)size, GIT_OBJ_BLOB); + git__free(link_data); } else { - int fd; - - if ((fd = p_open(path, O_RDONLY)) < 0) - return git__throw(GIT_ENOTFOUND, "Could not open '%s'", path); - - error = git_odb__hashfd(out, fd, (size_t)size, GIT_OBJ_BLOB); + int fd = git_futils_open_ro(path); + if (fd < 0) + return -1; + result = git_odb__hashfd(out, fd, (size_t)size, GIT_OBJ_BLOB); p_close(fd); } - return error; + return result; } int git_odb_hashfile(git_oid *out, const char *path, git_otype type) { - int fd, error; git_off_t size; - - if ((fd = p_open(path, O_RDONLY)) < 0) - return git__throw(GIT_ENOTFOUND, "Could not open '%s'", path); + int result, fd = git_futils_open_ro(path); + if (fd < 0) + return fd; if ((size = git_futils_filesize(fd)) < 0 || !git__is_sizet(size)) { + giterr_set(GITERR_OS, "File size overflow for 32-bit systems"); p_close(fd); - return git__throw(GIT_EOSERR, - "File size overflow. The object is too big to fit in 32-bit mode"); + return -1; } - error = git_odb__hashfd(out, fd, (size_t)size, type); - + result = git_odb__hashfd(out, fd, (size_t)size, type); p_close(fd); - return error; + return result; } int git_odb_hash(git_oid *id, const void *data, size_t len, git_otype type) @@ -242,11 +234,11 @@ static int fake_wstream__write(git_odb_stream *_stream, const char *data, size_t fake_wstream *stream = (fake_wstream *)_stream; if (stream->written + len > stream->size) - return GIT_ENOMEM; + return -1; memcpy(stream->buffer + stream->written, data, len); stream->written += len; - return GIT_SUCCESS; + return 0; } static void fake_wstream__free(git_odb_stream *_stream) @@ -262,15 +254,14 @@ static int init_fake_wstream(git_odb_stream **stream_p, git_odb_backend *backend fake_wstream *stream; stream = git__calloc(1, sizeof(fake_wstream)); - if (stream == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(stream); stream->size = size; stream->type = type; stream->buffer = git__malloc(size); if (stream->buffer == NULL) { git__free(stream); - return GIT_ENOMEM; + return -1; } stream->stream.backend = backend; @@ -281,7 +272,7 @@ static int init_fake_wstream(git_odb_stream **stream_p, git_odb_backend *backend stream->stream.mode = GIT_STREAM_WRONLY; *stream_p = (git_odb_stream *)stream; - return GIT_SUCCESS; + return 0; } /*********************************************************** @@ -305,26 +296,19 @@ static int backend_sort_cmp(const void *a, const void *b) int git_odb_new(git_odb **out) { - int error; - git_odb *db = git__calloc(1, sizeof(*db)); - if (!db) - return GIT_ENOMEM; - - error = git_cache_init(&db->cache, GIT_DEFAULT_CACHE_SIZE, &free_odb_object); - if (error < GIT_SUCCESS) { - git__free(db); - return git__rethrow(error, "Failed to create object database"); - } + GITERR_CHECK_ALLOC(db); - if ((error = git_vector_init(&db->backends, 4, backend_sort_cmp)) < GIT_SUCCESS) { + if (git_cache_init(&db->cache, GIT_DEFAULT_CACHE_SIZE, &free_odb_object) < 0 || + git_vector_init(&db->backends, 4, backend_sort_cmp) < 0) + { git__free(db); - return git__rethrow(error, "Failed to create object database"); + return -1; } *out = db; GIT_REFCOUNT_INC(db); - return GIT_SUCCESS; + return 0; } static int add_backend_internal(git_odb *odb, git_odb_backend *backend, int priority, int is_alternate) @@ -333,12 +317,11 @@ static int add_backend_internal(git_odb *odb, git_odb_backend *backend, int prio assert(odb && backend); - if (backend->odb != NULL && backend->odb != odb) - return git__throw(GIT_EBUSY, "The backend is already owned by another ODB"); + /* Check if the backend is already owned by another ODB */ + assert(!backend->odb || backend->odb == odb); internal = git__malloc(sizeof(backend_internal)); - if (internal == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(internal); internal->backend = backend; internal->priority = priority; @@ -346,12 +329,12 @@ static int add_backend_internal(git_odb *odb, git_odb_backend *backend, int prio if (git_vector_insert(&odb->backends, internal) < 0) { git__free(internal); - return GIT_ENOMEM; + return -1; } git_vector_sort(&odb->backends); internal->backend->odb = odb; - return GIT_SUCCESS; + return 0; } int git_odb_add_backend(git_odb *odb, git_odb_backend *backend, int priority) @@ -367,27 +350,18 @@ int git_odb_add_alternate(git_odb *odb, git_odb_backend *backend, int priority) static int add_default_backends(git_odb *db, const char *objects_dir, int as_alternates) { git_odb_backend *loose, *packed; - int error; /* add the loose object backend */ - error = git_odb_backend_loose(&loose, objects_dir, -1, 0); - if (error < GIT_SUCCESS) - return error; - - error = add_backend_internal(db, loose, GIT_LOOSE_PRIORITY, as_alternates); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to add backend"); + if (git_odb_backend_loose(&loose, objects_dir, -1, 0) < 0 || + add_backend_internal(db, loose, GIT_LOOSE_PRIORITY, as_alternates) < 0) + return -1; /* add the packed file backend */ - error = git_odb_backend_pack(&packed, objects_dir); - if (error < GIT_SUCCESS) - return error; - - error = add_backend_internal(db, packed, GIT_PACKED_PRIORITY, as_alternates); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to add backend"); + if (git_odb_backend_pack(&packed, objects_dir) < 0 || + add_backend_internal(db, packed, GIT_PACKED_PRIORITY, as_alternates) < 0) + return -1; - return GIT_SUCCESS; + return 0; } static int load_alternates(git_odb *odb, const char *objects_dir) @@ -396,24 +370,22 @@ static int load_alternates(git_odb *odb, const char *objects_dir) git_buf alternates_buf = GIT_BUF_INIT; char *buffer; const char *alternate; - int error; + int result = 0; - error = git_buf_joinpath(&alternates_path, objects_dir, GIT_ALTERNATES_FILE); - if (error < GIT_SUCCESS) - return error; + if (git_buf_joinpath(&alternates_path, objects_dir, GIT_ALTERNATES_FILE) < 0) + return -1; - if (git_path_exists(alternates_path.ptr) < GIT_SUCCESS) { + if (git_path_exists(alternates_path.ptr) == false) { git_buf_free(&alternates_path); - return GIT_SUCCESS; + return 0; } - if (git_futils_readbuffer(&alternates_buf, alternates_path.ptr) < GIT_SUCCESS) { + if (git_futils_readbuffer(&alternates_buf, alternates_path.ptr) < 0) { git_buf_free(&alternates_path); - return git__throw(GIT_EOSERR, "Failed to add backend. Can't read alternates"); + return -1; } buffer = (char *)alternates_buf.ptr; - error = GIT_SUCCESS; /* add each alternate as a new backend; one alternate per line */ while ((alternate = git__strtok(&buffer, "\r\n")) != NULL) { @@ -422,48 +394,41 @@ static int load_alternates(git_odb *odb, const char *objects_dir) /* relative path: build based on the current `objects` folder */ if (*alternate == '.') { - error = git_buf_joinpath(&alternates_path, objects_dir, alternate); - if (error < GIT_SUCCESS) + if ((result = git_buf_joinpath(&alternates_path, objects_dir, alternate)) < 0) break; alternate = git_buf_cstr(&alternates_path); } - if ((error = add_default_backends(odb, alternate, 1)) < GIT_SUCCESS) + if ((result = add_default_backends(odb, alternate, 1)) < 0) break; } git_buf_free(&alternates_path); git_buf_free(&alternates_buf); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to load alternates"); - return error; + return result; } int git_odb_open(git_odb **out, const char *objects_dir) { git_odb *db; - int error; assert(out && objects_dir); *out = NULL; - if ((error = git_odb_new(&db)) < 0) - return git__rethrow(error, "Failed to open ODB"); - - if ((error = add_default_backends(db, objects_dir, 0)) < GIT_SUCCESS) - goto cleanup; + if (git_odb_new(&db) < 0) + return -1; - if ((error = load_alternates(db, objects_dir)) < GIT_SUCCESS) - goto cleanup; + if (add_default_backends(db, objects_dir, 0) < 0 || + load_alternates(db, objects_dir) < 0) + { + git_odb_free(db); + return -1; + } *out = db; - return GIT_SUCCESS; - -cleanup: - git_odb_free(db); - return error; /* error already set - pass through */ + return 0; } static void odb_free(git_odb *db) @@ -497,13 +462,13 @@ int git_odb_exists(git_odb *db, const git_oid *id) { git_odb_object *object; unsigned int i; - int found = 0; + bool found = false; assert(db && id); if ((object = git_cache_get(&db->cache, id)) != NULL) { git_odb_object_free(object); - return 1; + return (int)true; } for (i = 0; i < db->backends.length && !found; ++i) { @@ -514,7 +479,7 @@ int git_odb_exists(git_odb *db, const git_oid *id) found = b->exists(b, id); } - return found; + return (int)found; } int git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *db, const git_oid *id) @@ -529,7 +494,7 @@ int git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *db, const git *len_p = object->raw.len; *type_p = object->raw.type; git_odb_object_free(object); - return GIT_SUCCESS; + return 0; } for (i = 0; i < db->backends.length && error < 0; ++i) { @@ -540,23 +505,20 @@ int git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *db, const git error = b->read_header(len_p, type_p, b, id); } - if (error == GIT_EPASSTHROUGH) - return GIT_SUCCESS; + if (!error || error == GIT_EPASSTHROUGH) + return 0; /* * no backend could read only the header. * try reading the whole object and freeing the contents */ - if (error < 0) { - if ((error = git_odb_read(&object, db, id)) < GIT_SUCCESS) - return error; /* error already set - pass through */ + if ((error = git_odb_read(&object, db, id)) < 0) + return error; /* error already set - pass along */ - *len_p = object->raw.len; - *type_p = object->raw.type; - git_odb_object_free(object); - } - - return GIT_SUCCESS; + *len_p = object->raw.len; + *type_p = object->raw.type; + git_odb_object_free(object); + return 0; } int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) @@ -569,7 +531,7 @@ int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) *out = git_cache_get(&db->cache, id); if (*out != NULL) - return GIT_SUCCESS; + return 0; for (i = 0; i < db->backends.length && error < 0; ++i) { backend_internal *internal = git_vector_get(&db->backends, i); @@ -579,15 +541,19 @@ int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) error = b->read(&raw.data, &raw.len, &raw.type, b, id); } - if (error == GIT_EPASSTHROUGH || error == GIT_SUCCESS) { - *out = git_cache_try_store(&db->cache, new_odb_object(id, &raw)); - return GIT_SUCCESS; - } + /* TODO: If no backends are configured, this returns GIT_ENOTFOUND but + * will never have called giterr_set(). + */ - return git__rethrow(error, "Failed to read object"); + if (error && error != GIT_EPASSTHROUGH) + return error; + + *out = git_cache_try_store(&db->cache, new_odb_object(id, &raw)); + return 0; } -int git_odb_read_prefix(git_odb_object **out, git_odb *db, const git_oid *short_id, unsigned int len) +int git_odb_read_prefix( + git_odb_object **out, git_odb *db, const git_oid *short_id, unsigned int len) { unsigned int i; int error = GIT_ENOTFOUND; @@ -598,7 +564,7 @@ int git_odb_read_prefix(git_odb_object **out, git_odb *db, const git_oid *short_ assert(out && db); if (len < GIT_OID_MINPREFIXLEN) - return git__throw(GIT_EAMBIGUOUSOIDPREFIX, "Failed to lookup object. Prefix length is lower than %d.", GIT_OID_MINPREFIXLEN); + return git_odb__error_ambiguous("prefix length too short"); if (len > GIT_OID_HEXSZ) len = GIT_OID_HEXSZ; @@ -606,7 +572,7 @@ int git_odb_read_prefix(git_odb_object **out, git_odb *db, const git_oid *short_ if (len == GIT_OID_HEXSZ) { *out = git_cache_get(&db->cache, short_id); if (*out != NULL) - return GIT_SUCCESS; + return 0; } for (i = 0; i < db->backends.length && found < 2; ++i) { @@ -615,33 +581,24 @@ int git_odb_read_prefix(git_odb_object **out, git_odb *db, const git_oid *short_ if (b->read != NULL) { error = b->read_prefix(&full_oid, &raw.data, &raw.len, &raw.type, b, short_id, len); - switch (error) { - case GIT_SUCCESS: + if (!error) found++; - break; - case GIT_ENOTFOUND: - case GIT_EPASSTHROUGH: - break; - case GIT_EAMBIGUOUSOIDPREFIX: - return git__rethrow(error, "Failed to read object. Ambiguous sha1 prefix"); - default: - return git__rethrow(error, "Failed to read object"); - } + else if (error != GIT_ENOTFOUND && error != GIT_EPASSTHROUGH) + return error; } } - if (found == 1) { - *out = git_cache_try_store(&db->cache, new_odb_object(&full_oid, &raw)); - } else if (found > 1) { - return git__throw(GIT_EAMBIGUOUSOIDPREFIX, "Failed to read object. Ambiguous sha1 prefix"); - } else { - return git__throw(GIT_ENOTFOUND, "Failed to read object. Object not found"); - } + if (found == 0) + return git_odb__error_notfound("no match for prefix"); + if (found > 1) + return git_odb__error_ambiguous("multiple matches for prefix"); - return GIT_SUCCESS; + *out = git_cache_try_store(&db->cache, new_odb_object(&full_oid, &raw)); + return 0; } -int git_odb_write(git_oid *oid, git_odb *db, const void *data, size_t len, git_otype type) +int git_odb_write( + git_oid *oid, git_odb *db, const void *data, size_t len, git_otype type) { unsigned int i; int error = GIT_ERROR; @@ -661,24 +618,25 @@ int git_odb_write(git_oid *oid, git_odb *db, const void *data, size_t len, git_o error = b->write(oid, b, data, len, type); } - if (error == GIT_EPASSTHROUGH || error == GIT_SUCCESS) - return GIT_SUCCESS; + if (!error || error == GIT_EPASSTHROUGH) + return 0; /* if no backends were able to write the object directly, we try a streaming * write to the backends; just write the whole object into the stream in one * push */ - if ((error = git_odb_open_wstream(&stream, db, len, type)) == GIT_SUCCESS) { - stream->write(stream, data, len); - error = stream->finalize_write(oid, stream); - stream->free(stream); - return GIT_SUCCESS; - } + if ((error = git_odb_open_wstream(&stream, db, len, type)) != 0) + return error; + + stream->write(stream, data, len); + error = stream->finalize_write(oid, stream); + stream->free(stream); - return git__rethrow(error, "Failed to write object"); + return error; } -int git_odb_open_wstream(git_odb_stream **stream, git_odb *db, size_t size, git_otype type) +int git_odb_open_wstream( + git_odb_stream **stream, git_odb *db, size_t size, git_otype type) { unsigned int i; int error = GIT_ERROR; @@ -699,10 +657,10 @@ int git_odb_open_wstream(git_odb_stream **stream, git_odb *db, size_t size, git_ error = init_fake_wstream(stream, b, size, type); } - if (error == GIT_EPASSTHROUGH || error == GIT_SUCCESS) - return GIT_SUCCESS; + if (error == GIT_EPASSTHROUGH) + error = 0; - return git__rethrow(error, "Failed to open write stream"); + return error; } int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oid) @@ -720,9 +678,21 @@ int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oi error = b->readstream(stream, b, oid); } - if (error == GIT_EPASSTHROUGH || error == GIT_SUCCESS) - return GIT_SUCCESS; + if (error == GIT_EPASSTHROUGH) + error = 0; - return git__rethrow(error, "Failed to open read stream"); + return error; +} + +int git_odb__error_notfound(const char *message) +{ + giterr_set(GITERR_ODB, "Object not found - %s", message); + return GIT_ENOTFOUND; +} + +int git_odb__error_ambiguous(const char *message) +{ + giterr_set(GITERR_ODB, "Ambiguous SHA1 prefix - %s", message); + return GIT_EAMBIGUOUS; } @@ -67,4 +67,14 @@ int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type); */ int git_odb__hashlink(git_oid *out, const char *path); +/* + * Generate a GIT_ENOTFOUND error for the ODB. + */ +int git_odb__error_notfound(const char *message); + +/* + * Generate a GIT_EAMBIGUOUS error for the ODB. + */ +int git_odb__error_ambiguous(const char *message); + #endif diff --git a/src/odb_loose.c b/src/odb_loose.c index f5f6e35ac..d028deca5 100644 --- a/src/odb_loose.c +++ b/src/odb_loose.c @@ -61,17 +61,17 @@ static int object_file_name(git_buf *name, const char *dir, const git_oid *id) git_buf_sets(name, dir); /* expand length for 40 hex sha1 chars + 2 * '/' + '\0' */ - if (git_buf_grow(name, name->size + GIT_OID_HEXSZ + 3) < GIT_SUCCESS) - return GIT_ENOMEM; + if (git_buf_grow(name, git_buf_len(name) + GIT_OID_HEXSZ + 3) < 0) + return -1; git_path_to_dir(name); /* loose object filename: aa/aaa... (41 bytes) */ - git_oid_pathfmt(name->ptr + name->size, id); + git_oid_pathfmt(name->ptr + git_buf_len(name), id); name->size += GIT_OID_HEXSZ + 1; name->ptr[name->size] = '\0'; - return GIT_SUCCESS; + return 0; } @@ -81,7 +81,7 @@ static size_t get_binary_object_header(obj_hdr *hdr, git_buf *obj) unsigned char *data = (unsigned char *)obj->ptr; size_t shift, size, used = 0; - if (obj->size == 0) + if (git_buf_len(obj) == 0) return 0; c = data[used++]; @@ -90,7 +90,7 @@ static size_t get_binary_object_header(obj_hdr *hdr, git_buf *obj) size = c & 15; shift = 4; while (c & 0x80) { - if (obj->size <= used) + if (git_buf_len(obj) <= used) return 0; if (sizeof(size_t) * 8 <= shift) return 0; @@ -182,7 +182,7 @@ static int start_inflate(z_stream *s, git_buf *obj, void *out, size_t len) int status; init_stream(s, out, len); - set_stream_input(s, obj->ptr, obj->size); + set_stream_input(s, obj->ptr, git_buf_len(obj)); if ((status = inflateInit(s)) < Z_OK) return status; @@ -199,10 +199,12 @@ static int finish_inflate(z_stream *s) inflateEnd(s); - if ((status != Z_STREAM_END) || (s->avail_in != 0)) - return git__throw(GIT_ERROR, "Failed to finish inflation. Stream aborted prematurely"); + if ((status != Z_STREAM_END) || (s->avail_in != 0)) { + giterr_set(GITERR_ZLIB, "Failed to finish ZLib inflation. Stream aborted prematurely"); + return -1; + } - return GIT_SUCCESS; + return 0; } static int is_zlib_compressed_data(unsigned char *data) @@ -226,21 +228,24 @@ static int inflate_buffer(void *in, size_t inlen, void *out, size_t outlen) zs.next_in = in; zs.avail_in = (uInt)inlen; - if (inflateInit(&zs) < Z_OK) - return git__throw(GIT_ERROR, "Failed to inflate buffer"); + if (inflateInit(&zs) < Z_OK) { + giterr_set(GITERR_ZLIB, "Failed to inflate buffer"); + return -1; + } while (status == Z_OK) status = inflate(&zs, Z_FINISH); inflateEnd(&zs); - if ((status != Z_STREAM_END) /*|| (zs.avail_in != 0) */) - return git__throw(GIT_ERROR, "Failed to inflate buffer. Stream aborted prematurely"); - - if (zs.total_out != outlen) - return git__throw(GIT_ERROR, "Failed to inflate buffer. Stream aborted prematurely"); + if (status != Z_STREAM_END /* || zs.avail_in != 0 */ || + zs.total_out != outlen) + { + giterr_set(GITERR_ZLIB, "Failed to inflate buffer. Stream aborted prematurely"); + return -1; + } - return GIT_SUCCESS; + return 0; } static void *inflate_tail(z_stream *s, void *hb, size_t used, obj_hdr *hdr) @@ -297,24 +302,23 @@ static int inflate_packlike_loose_disk_obj(git_rawobj *out, git_buf *obj) * read the object header, which is an (uncompressed) * binary encoding of the object type and size. */ - if ((used = get_binary_object_header(&hdr, obj)) == 0) - return git__throw(GIT_ERROR, "Failed to inflate loose object. Object has no header"); - - if (!git_object_typeisloose(hdr.type)) - return git__throw(GIT_ERROR, "Failed to inflate loose object. Wrong object type"); + if ((used = get_binary_object_header(&hdr, obj)) == 0 || + !git_object_typeisloose(hdr.type)) { + giterr_set(GITERR_ODB, "Failed to inflate loose object."); + return -1; + } /* * allocate a buffer and inflate the data into it */ buf = git__malloc(hdr.size + 1); - if (!buf) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(buf); in = ((unsigned char *)obj->ptr) + used; len = obj->size - used; - if (inflate_buffer(in, len, buf, hdr.size)) { + if (inflate_buffer(in, len, buf, hdr.size) < 0) { git__free(buf); - return git__throw(GIT_ERROR, "Failed to inflate loose object. Could not inflate buffer"); + return -1; } buf[hdr.size] = '\0'; @@ -322,7 +326,7 @@ static int inflate_packlike_loose_disk_obj(git_rawobj *out, git_buf *obj) out->len = hdr.size; out->type = hdr.type; - return GIT_SUCCESS; + return 0; } static int inflate_disk_obj(git_rawobj *out, git_buf *obj) @@ -342,28 +346,27 @@ static int inflate_disk_obj(git_rawobj *out, git_buf *obj) * inflate the initial part of the io buffer in order * to parse the object header (type and size). */ - if (start_inflate(&zs, obj, head, sizeof(head)) < Z_OK) - return git__throw(GIT_ERROR, "Failed to inflate disk object. Could not inflate buffer"); - - if ((used = get_object_header(&hdr, head)) == 0) - return git__throw(GIT_ERROR, "Failed to inflate disk object. Object has no header"); - - if (!git_object_typeisloose(hdr.type)) - return git__throw(GIT_ERROR, "Failed to inflate disk object. Wrong object type"); + if (start_inflate(&zs, obj, head, sizeof(head)) < Z_OK || + (used = get_object_header(&hdr, head)) == 0 || + !git_object_typeisloose(hdr.type)) + { + giterr_set(GITERR_ODB, "Failed to inflate disk object."); + return -1; + } /* * allocate a buffer and inflate the object data into it * (including the initial sequence in the head buffer). */ if ((buf = inflate_tail(&zs, head, used, &hdr)) == NULL) - return GIT_ENOMEM; + return -1; buf[hdr.size] = '\0'; out->data = buf; out->len = hdr.size; out->type = hdr.type; - return GIT_SUCCESS; + return 0; } @@ -387,25 +390,24 @@ static int read_loose(git_rawobj *out, git_buf *loc) assert(out && loc); - if ((error = git_buf_lasterror(loc)) < GIT_SUCCESS) - return error; + if (git_buf_oom(loc)) + return -1; out->data = NULL; out->len = 0; out->type = GIT_OBJ_BAD; - if (git_futils_readbuffer(&obj, loc->ptr) < 0) - return git__throw(GIT_ENOTFOUND, "Failed to read loose object. File not found"); + if (!(error = git_futils_readbuffer(&obj, loc->ptr))) + error = inflate_disk_obj(out, &obj); - error = inflate_disk_obj(out, &obj); git_buf_free(&obj); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to read loose object"); + return error; } static int read_header_loose(git_rawobj *out, git_buf *loc) { - int error = GIT_SUCCESS, z_return = Z_ERRNO, read_bytes; + int error = 0, z_return = Z_ERRNO, read_bytes; git_file fd; z_stream zs; obj_hdr header_obj; @@ -413,49 +415,41 @@ static int read_header_loose(git_rawobj *out, git_buf *loc) assert(out && loc); - if ((error = git_buf_lasterror(loc)) < GIT_SUCCESS) - return error; + if (git_buf_oom(loc)) + return -1; out->data = NULL; - if ((fd = p_open(loc->ptr, O_RDONLY)) < 0) - return git__throw(GIT_ENOTFOUND, "Failed to read loose object header. File not found"); + if ((fd = git_futils_open_ro(loc->ptr)) < 0) + return fd; init_stream(&zs, inflated_buffer, sizeof(inflated_buffer)); - if (inflateInit(&zs) < Z_OK) { - error = GIT_EZLIB; - goto cleanup; - } + z_return = inflateInit(&zs); - do { - if ((read_bytes = read(fd, raw_buffer, sizeof(raw_buffer))) > 0) { + while (z_return == Z_OK) { + if ((read_bytes = p_read(fd, raw_buffer, sizeof(raw_buffer))) > 0) { set_stream_input(&zs, raw_buffer, read_bytes); z_return = inflate(&zs, 0); - } else { + } else z_return = Z_STREAM_END; - break; - } - } while (z_return == Z_OK); + } if ((z_return != Z_STREAM_END && z_return != Z_BUF_ERROR) || get_object_header(&header_obj, inflated_buffer) == 0 - || git_object_typeisloose(header_obj.type) == 0) { - error = GIT_EOBJCORRUPTED; - goto cleanup; + || git_object_typeisloose(header_obj.type) == 0) + { + giterr_set(GITERR_ZLIB, "Failed to read loose object header"); + error = -1; + } else { + out->len = header_obj.size; + out->type = header_obj.type; } - out->len = header_obj.size; - out->type = header_obj.type; - -cleanup: finish_inflate(&zs); p_close(fd); - if (error < GIT_SUCCESS) - return git__throw(error, "Failed to read loose object header. Header is corrupted"); - - return GIT_SUCCESS; + return error; } static int locate_object( @@ -465,8 +459,8 @@ static int locate_object( { int error = object_file_name(object_location, backend->objects_dir, oid); - if (error == GIT_SUCCESS) - error = git_path_exists(git_buf_cstr(object_location)); + if (!error && !git_path_exists(object_location->ptr)) + return GIT_ENOTFOUND; return error; } @@ -475,12 +469,12 @@ static int locate_object( static int fn_locate_object_short_oid(void *state, git_buf *pathbuf) { loose_locate_object_state *sstate = (loose_locate_object_state *)state; - if (pathbuf->size - sstate->dir_len != GIT_OID_HEXSZ - 2) { + if (git_buf_len(pathbuf) - sstate->dir_len != GIT_OID_HEXSZ - 2) { /* Entry cannot be an object. Continue to next entry */ - return GIT_SUCCESS; + return 0; } - if (!git_path_exists(pathbuf->ptr) && git_path_isdir(pathbuf->ptr)) { + if (git_path_isdir(pathbuf->ptr) == false) { /* We are already in the directory matching the 2 first hex characters, * compare the first ncmp characters of the oids */ if (!memcmp(sstate->short_oid + 2, @@ -495,10 +489,11 @@ static int fn_locate_object_short_oid(void *state, git_buf *pathbuf) { sstate->found++; } } + if (sstate->found > 1) - return git__throw(GIT_EAMBIGUOUSOIDPREFIX, "Ambiguous sha1 prefix within loose objects"); + return git_odb__error_ambiguous("multiple matches in loose objects"); - return GIT_SUCCESS; + return 0; } /* Locate an object matching a given short oid */ @@ -515,60 +510,56 @@ static int locate_object_short_oid( int error; /* prealloc memory for OBJ_DIR/xx/ */ - if ((error = git_buf_grow(object_location, dir_len + 5)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to locate object from short oid"); + if (git_buf_grow(object_location, dir_len + 5) < 0) + return -1; git_buf_sets(object_location, objects_dir); git_path_to_dir(object_location); /* save adjusted position at end of dir so it can be restored later */ - dir_len = object_location->size; + dir_len = git_buf_len(object_location); /* Convert raw oid to hex formatted oid */ git_oid_fmt((char *)state.short_oid, short_oid); /* Explore OBJ_DIR/xx/ where xx is the beginning of hex formatted short oid */ - error = git_buf_printf(object_location, "%.2s/", state.short_oid); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to locate object from short oid"); + if (git_buf_printf(object_location, "%.2s/", state.short_oid) < 0) + return -1; /* Check that directory exists */ - if (git_path_exists(object_location->ptr) || - git_path_isdir(object_location->ptr)) - return git__throw(GIT_ENOTFOUND, "Failed to locate object from short oid. Object not found"); + if (git_path_isdir(object_location->ptr) == false) + return git_odb__error_notfound("failed to locate from short oid"); - state.dir_len = object_location->size; + state.dir_len = git_buf_len(object_location); state.short_oid_len = len; state.found = 0; /* Explore directory to find a unique object matching short_oid */ - error = git_path_direach(object_location, fn_locate_object_short_oid, &state); + error = git_path_direach( + object_location, fn_locate_object_short_oid, &state); if (error) - return git__rethrow(error, "Failed to locate object from short oid"); + return error; - if (!state.found) { - return git__throw(GIT_ENOTFOUND, "Failed to locate object from short oid. Object not found"); - } + if (!state.found) + return git_odb__error_notfound("failed to locate from short oid"); /* Convert obtained hex formatted oid to raw */ error = git_oid_fromstr(res_oid, (char *)state.res_oid); - if (error) { - return git__rethrow(error, "Failed to locate object from short oid"); - } + if (error) + return error; /* Update the location according to the oid obtained */ git_buf_truncate(object_location, dir_len); - error = git_buf_grow(object_location, dir_len + GIT_OID_HEXSZ + 2); - if (error) - return git__rethrow(error, "Failed to locate object from short oid"); + if (git_buf_grow(object_location, dir_len + GIT_OID_HEXSZ + 2) < 0) + return -1; git_oid_pathfmt(object_location->ptr + dir_len, res_oid); object_location->size += GIT_OID_HEXSZ + 1; object_location->ptr[object_location->size] = '\0'; - return GIT_SUCCESS; + return 0; } @@ -591,7 +582,7 @@ static int loose_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_ { git_buf object_path = GIT_BUF_INIT; git_rawobj raw; - int error = GIT_SUCCESS; + int error; assert(backend && oid); @@ -599,8 +590,8 @@ static int loose_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_ raw.type = GIT_OBJ_BAD; if (locate_object(&object_path, (loose_backend *)backend, oid) < 0) - error = git__throw(GIT_ENOTFOUND, "Failed to read loose backend header. Object not found"); - else if ((error = read_header_loose(&raw, &object_path)) == GIT_SUCCESS) { + error = git_odb__error_notfound("in loose backend"); + else if ((error = read_header_loose(&raw, &object_path)) == 0) { *len_p = raw.len; *type_p = raw.type; } @@ -614,20 +605,17 @@ static int loose_backend__read(void **buffer_p, size_t *len_p, git_otype *type_p { git_buf object_path = GIT_BUF_INIT; git_rawobj raw; - int error = GIT_SUCCESS; + int error = 0; assert(backend && oid); if (locate_object(&object_path, (loose_backend *)backend, oid) < 0) - error = git__throw(GIT_ENOTFOUND, "Failed to read loose backend. Object not found"); - else if ((error = read_loose(&raw, &object_path)) == GIT_SUCCESS) { + error = git_odb__error_notfound("in loose backend"); + else if ((error = read_loose(&raw, &object_path)) == 0) { *buffer_p = raw.data; *len_p = raw.len; *type_p = raw.type; } - else { - git__rethrow(error, "Failed to read loose backend"); - } git_buf_free(&object_path); @@ -643,16 +631,15 @@ static int loose_backend__read_prefix( const git_oid *short_oid, unsigned int len) { - int error = GIT_SUCCESS; + int error = 0; if (len < GIT_OID_MINPREFIXLEN) - return git__throw(GIT_EAMBIGUOUSOIDPREFIX, "Failed to read loose " - "backend. Prefix length is lower than %d.", GIT_OID_MINPREFIXLEN); + error = git_odb__error_ambiguous("prefix length too short"); - if (len >= GIT_OID_HEXSZ) { + else if (len >= GIT_OID_HEXSZ) { /* We can fall back to regular read method */ error = loose_backend__read(buffer_p, len_p, type_p, backend, short_oid); - if (error == GIT_SUCCESS) + if (!error) git_oid_cpy(out_oid, short_oid); } else { git_buf object_path = GIT_BUF_INIT; @@ -661,11 +648,9 @@ static int loose_backend__read_prefix( assert(backend && short_oid); if ((error = locate_object_short_oid(&object_path, out_oid, - (loose_backend *)backend, short_oid, len)) < 0) - git__rethrow(error, "Failed to read loose backend"); - else if ((error = read_loose(&raw, &object_path)) < GIT_SUCCESS) - git__rethrow(error, "Failed to read loose backend"); - else { + (loose_backend *)backend, short_oid, len)) == 0 && + (error = read_loose(&raw, &object_path)) == 0) + { *buffer_p = raw.data; *len_p = raw.len; *type_p = raw.type; @@ -688,47 +673,33 @@ static int loose_backend__exists(git_odb_backend *backend, const git_oid *oid) git_buf_free(&object_path); - return (error == GIT_SUCCESS); + return !error; } static int loose_backend__stream_fwrite(git_oid *oid, git_odb_stream *_stream) { loose_writestream *stream = (loose_writestream *)_stream; loose_backend *backend = (loose_backend *)_stream->backend; - - int error; git_buf final_path = GIT_BUF_INIT; + int error = 0; - if ((error = git_filebuf_hash(oid, &stream->fbuf)) < GIT_SUCCESS) - goto cleanup; - - if ((error = object_file_name(&final_path, backend->objects_dir, oid)) < GIT_SUCCESS) - goto cleanup; - - if ((error = git_buf_lasterror(&final_path)) < GIT_SUCCESS) - goto cleanup; - - if ((error = git_futils_mkpath2file(final_path.ptr, GIT_OBJECT_DIR_MODE)) < GIT_SUCCESS) - goto cleanup; - + if (git_filebuf_hash(oid, &stream->fbuf) < 0 || + object_file_name(&final_path, backend->objects_dir, oid) < 0 || + git_futils_mkpath2file(final_path.ptr, GIT_OBJECT_DIR_MODE) < 0) + error = -1; /* * Don't try to add an existing object to the repository. This * is what git does and allows us to sidestep the fact that * we're not allowed to overwrite a read-only file on Windows. */ - if (git_path_exists(final_path.ptr) == GIT_SUCCESS) { + else if (git_path_exists(final_path.ptr) == true) git_filebuf_cleanup(&stream->fbuf); - goto cleanup; - } + else + error = git_filebuf_commit_at( + &stream->fbuf, final_path.ptr, GIT_OBJECT_FILE_MODE); - error = git_filebuf_commit_at(&stream->fbuf, final_path.ptr, GIT_OBJECT_FILE_MODE); - -cleanup: git_buf_free(&final_path); - if (error < GIT_SUCCESS) - git__rethrow(error, "Failed to write loose backend"); - return error; } @@ -752,22 +723,18 @@ static int format_object_header(char *hdr, size_t n, size_t obj_len, git_otype o int len = snprintf(hdr, n, "%s %"PRIuZ, type_str, obj_len); assert(len > 0); /* otherwise snprintf() is broken */ - assert(((size_t) len) < n); /* otherwise the caller is broken! */ + assert(((size_t)len) < n); /* otherwise the caller is broken! */ - if (len < 0 || ((size_t) len) >= n) - return git__throw(GIT_ERROR, "Failed to format object header. Length is out of bounds"); return len+1; } static int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_backend, size_t length, git_otype type) { loose_backend *backend; - loose_writestream *stream; - + loose_writestream *stream = NULL; char hdr[64]; git_buf tmp_path = GIT_BUF_INIT; int hdrlen; - int error; assert(_backend); @@ -775,12 +742,9 @@ static int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_ *stream_out = NULL; hdrlen = format_object_header(hdr, sizeof(hdr), length, type); - if (hdrlen < GIT_SUCCESS) - return git__throw(GIT_EOBJCORRUPTED, "Failed to create loose backend stream. Object is corrupted"); stream = git__calloc(1, sizeof(loose_writestream)); - if (stream == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(stream); stream->stream.backend = _backend; stream->stream.read = NULL; /* read only */ @@ -789,36 +753,26 @@ static int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_ stream->stream.free = &loose_backend__stream_free; stream->stream.mode = GIT_STREAM_WRONLY; - error = git_buf_joinpath(&tmp_path, backend->objects_dir, "tmp_object"); - if (error < GIT_SUCCESS) - goto cleanup; - - error = git_filebuf_open(&stream->fbuf, tmp_path.ptr, - GIT_FILEBUF_HASH_CONTENTS | - GIT_FILEBUF_TEMPORARY | - (backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT)); - if (error < GIT_SUCCESS) - goto cleanup; - - error = stream->stream.write((git_odb_stream *)stream, hdr, hdrlen); - if (error < GIT_SUCCESS) - goto cleanup; - + if (git_buf_joinpath(&tmp_path, backend->objects_dir, "tmp_object") < 0 || + git_filebuf_open(&stream->fbuf, tmp_path.ptr, + GIT_FILEBUF_HASH_CONTENTS | + GIT_FILEBUF_TEMPORARY | + (backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT)) < 0 || + stream->stream.write((git_odb_stream *)stream, hdr, hdrlen) < 0) + { + git_filebuf_cleanup(&stream->fbuf); + git__free(stream); + stream = NULL; + } git_buf_free(&tmp_path); - *stream_out = (git_odb_stream *)stream; - return GIT_SUCCESS; -cleanup: - git_buf_free(&tmp_path); - git_filebuf_cleanup(&stream->fbuf); - git__free(stream); - return git__rethrow(error, "Failed to create loose backend stream"); + return !stream ? -1 : 0; } static int loose_backend__write(git_oid *oid, git_odb_backend *_backend, const void *data, size_t len, git_otype type) { - int error, header_len; + int error = 0, header_len; git_buf final_path = GIT_BUF_INIT; char header[64]; git_filebuf fbuf = GIT_FILEBUF_INIT; @@ -827,39 +781,29 @@ static int loose_backend__write(git_oid *oid, git_odb_backend *_backend, const v backend = (loose_backend *)_backend; /* prepare the header for the file */ - { - header_len = format_object_header(header, sizeof(header), len, type); - if (header_len < GIT_SUCCESS) - return GIT_EOBJCORRUPTED; - } - - error = git_buf_joinpath(&final_path, backend->objects_dir, "tmp_object"); - if (error < GIT_SUCCESS) - goto cleanup; + header_len = format_object_header(header, sizeof(header), len, type); - error = git_filebuf_open(&fbuf, final_path.ptr, - GIT_FILEBUF_HASH_CONTENTS | - GIT_FILEBUF_TEMPORARY | - (backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT)); - if (error < GIT_SUCCESS) + if (git_buf_joinpath(&final_path, backend->objects_dir, "tmp_object") < 0 || + git_filebuf_open(&fbuf, final_path.ptr, + GIT_FILEBUF_HASH_CONTENTS | + GIT_FILEBUF_TEMPORARY | + (backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT)) < 0) + { + error = -1; goto cleanup; + } git_filebuf_write(&fbuf, header, header_len); git_filebuf_write(&fbuf, data, len); git_filebuf_hash(oid, &fbuf); - error = object_file_name(&final_path, backend->objects_dir, oid); - if (error < GIT_SUCCESS) - goto cleanup; - - error = git_futils_mkpath2file(final_path.ptr, GIT_OBJECT_DIR_MODE); - if (error < GIT_SUCCESS) - goto cleanup; - - error = git_filebuf_commit_at(&fbuf, final_path.ptr, GIT_OBJECT_FILE_MODE); + if (object_file_name(&final_path, backend->objects_dir, oid) < 0 || + git_futils_mkpath2file(final_path.ptr, GIT_OBJECT_DIR_MODE) < 0 || + git_filebuf_commit_at(&fbuf, final_path.ptr, GIT_OBJECT_FILE_MODE) < 0) + error = -1; cleanup: - if (error < GIT_SUCCESS) + if (error < 0) git_filebuf_cleanup(&fbuf); git_buf_free(&final_path); return error; @@ -884,14 +828,10 @@ int git_odb_backend_loose( loose_backend *backend; backend = git__calloc(1, sizeof(loose_backend)); - if (backend == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(backend); backend->objects_dir = git__strdup(objects_dir); - if (backend->objects_dir == NULL) { - git__free(backend); - return GIT_ENOMEM; - } + GITERR_CHECK_ALLOC(backend->objects_dir); if (compression_level < 0) compression_level = Z_BEST_SPEED; @@ -908,5 +848,5 @@ int git_odb_backend_loose( backend->parent.free = &loose_backend__free; *backend_out = (git_odb_backend *)backend; - return GIT_SUCCESS; + return 0; } diff --git a/src/odb_pack.c b/src/odb_pack.c index 249144a3a..242200b4a 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -137,19 +137,19 @@ static int packfile_load__cb(void *_data, git_buf *path); static int packfile_refresh_all(struct pack_backend *backend); static int pack_entry_find(struct git_pack_entry *e, - struct pack_backend *backend, const git_oid *oid); + struct pack_backend *backend, const git_oid *oid); /* Can find the offset of an object given * a prefix of an identifier. - * Throws GIT_EAMBIGUOUSOIDPREFIX if short oid - * is ambiguous. + * Sets GIT_EAMBIGUOUS if short oid is ambiguous. * This method assumes that len is between * GIT_OID_MINPREFIXLEN and GIT_OID_HEXSZ. */ -static int pack_entry_find_prefix(struct git_pack_entry *e, - struct pack_backend *backend, - const git_oid *short_oid, - unsigned int len); +static int pack_entry_find_prefix( + struct git_pack_entry *e, + struct pack_backend *backend, + const git_oid *short_oid, + unsigned int len); @@ -212,30 +212,25 @@ static int packfile_load__cb(void *_data, git_buf *path) struct pack_backend *backend = (struct pack_backend *)_data; struct git_pack_file *pack; int error; - size_t i; + unsigned int i; if (git__suffixcmp(path->ptr, ".idx") != 0) - return GIT_SUCCESS; /* not an index */ + return 0; /* not an index */ for (i = 0; i < backend->packs.length; ++i) { struct git_pack_file *p = git_vector_get(&backend->packs, i); - if (memcmp(p->pack_name, path->ptr, path->size - strlen(".idx")) == 0) - return GIT_SUCCESS; + if (memcmp(p->pack_name, git_buf_cstr(path), git_buf_len(path) - strlen(".idx")) == 0) + return 0; } error = git_packfile_check(&pack, path->ptr); - if (error == GIT_ENOTFOUND) { + if (error == GIT_ENOTFOUND) /* ignore missing .pack file as git does */ - return GIT_SUCCESS; - } else if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to load packfile"); - - if (git_vector_insert(&backend->packs, pack) < GIT_SUCCESS) { - git__free(pack); - return GIT_ENOMEM; - } + return 0; + else if (error < 0) + return error; - return GIT_SUCCESS; + return git_vector_insert(&backend->packs, pack); } static int packfile_refresh_all(struct pack_backend *backend) @@ -244,10 +239,10 @@ static int packfile_refresh_all(struct pack_backend *backend) struct stat st; if (backend->pack_folder == NULL) - return GIT_SUCCESS; + return 0; if (p_stat(backend->pack_folder, &st) < 0 || !S_ISDIR(st.st_mode)) - return git__throw(GIT_ENOTFOUND, "Failed to refresh packfiles. Backend not found"); + return git_odb__error_notfound("failed to refresh packfiles"); if (st.st_mtime != backend->pack_folder_mtime) { git_buf path = GIT_BUF_INIT; @@ -257,27 +252,28 @@ static int packfile_refresh_all(struct pack_backend *backend) error = git_path_direach(&path, packfile_load__cb, (void *)backend); git_buf_free(&path); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to refresh packfiles"); + + if (error < 0) + return error; git_vector_sort(&backend->packs); backend->pack_folder_mtime = st.st_mtime; } - return GIT_SUCCESS; + return 0; } static int pack_entry_find(struct git_pack_entry *e, struct pack_backend *backend, const git_oid *oid) { int error; - size_t i; + unsigned int i; - if ((error = packfile_refresh_all(backend)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to find pack entry"); + if ((error = packfile_refresh_all(backend)) < 0) + return error; if (backend->last_found && - git_pack_entry_find(e, backend->last_found, oid, GIT_OID_HEXSZ) == GIT_SUCCESS) - return GIT_SUCCESS; + git_pack_entry_find(e, backend->last_found, oid, GIT_OID_HEXSZ) == 0) + return 0; for (i = 0; i < backend->packs.length; ++i) { struct git_pack_file *p; @@ -286,13 +282,13 @@ static int pack_entry_find(struct git_pack_entry *e, struct pack_backend *backen if (p == backend->last_found) continue; - if (git_pack_entry_find(e, p, oid, GIT_OID_HEXSZ) == GIT_SUCCESS) { + if (git_pack_entry_find(e, p, oid, GIT_OID_HEXSZ) == 0) { backend->last_found = p; - return GIT_SUCCESS; + return 0; } } - return git__throw(GIT_ENOTFOUND, "Failed to find pack entry"); + return git_odb__error_notfound("failed to find pack entry"); } static int pack_entry_find_prefix( @@ -302,19 +298,18 @@ static int pack_entry_find_prefix( unsigned int len) { int error; - size_t i; + unsigned int i; unsigned found = 0; - if ((error = packfile_refresh_all(backend)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to find pack entry"); + if ((error = packfile_refresh_all(backend)) < 0) + return error; if (backend->last_found) { error = git_pack_entry_find(e, backend->last_found, short_oid, len); - if (error == GIT_EAMBIGUOUSOIDPREFIX) { - return git__rethrow(error, "Failed to find pack entry. Ambiguous sha1 prefix"); - } else if (error == GIT_SUCCESS) { + if (error == GIT_EAMBIGUOUS) + return error; + if (!error) found = 1; - } } for (i = 0; i < backend->packs.length; ++i) { @@ -325,24 +320,21 @@ static int pack_entry_find_prefix( continue; error = git_pack_entry_find(e, p, short_oid, len); - if (error == GIT_EAMBIGUOUSOIDPREFIX) { - return git__rethrow(error, "Failed to find pack entry. Ambiguous sha1 prefix"); - } else if (error == GIT_SUCCESS) { - found++; - if (found > 1) + if (error == GIT_EAMBIGUOUS) + return error; + if (!error) { + if (++found > 1) break; backend->last_found = p; } } - if (!found) { - return git__rethrow(GIT_ENOTFOUND, "Failed to find pack entry"); - } else if (found > 1) { - return git__rethrow(GIT_EAMBIGUOUSOIDPREFIX, "Failed to find pack entry. Ambiguous sha1 prefix"); - } else { - return GIT_SUCCESS; - } - + if (!found) + return git_odb__error_notfound("failed to find pack entry"); + else if (found > 1) + return git_odb__error_ambiguous("found multiple pack entries"); + else + return 0; } @@ -374,17 +366,15 @@ static int pack_backend__read(void **buffer_p, size_t *len_p, git_otype *type_p, git_rawobj raw; int error; - if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to read pack backend"); - - if ((error = git_packfile_unpack(&raw, e.p, &e.offset)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to read pack backend"); + if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < 0 || + (error = git_packfile_unpack(&raw, e.p, &e.offset)) < 0) + return error; *buffer_p = raw.data; *len_p = raw.len; *type_p = raw.type; - return GIT_SUCCESS; + return 0; } static int pack_backend__read_prefix( @@ -396,46 +386,44 @@ static int pack_backend__read_prefix( const git_oid *short_oid, unsigned int len) { + int error = 0; + if (len < GIT_OID_MINPREFIXLEN) - return git__throw(GIT_EAMBIGUOUSOIDPREFIX, "Failed to read pack backend. Prefix length is lower than %d.", GIT_OID_MINPREFIXLEN); + error = git_odb__error_ambiguous("prefix length too short"); - if (len >= GIT_OID_HEXSZ) { + else if (len >= GIT_OID_HEXSZ) { /* We can fall back to regular read method */ - int error = pack_backend__read(buffer_p, len_p, type_p, backend, short_oid); - if (error == GIT_SUCCESS) + error = pack_backend__read(buffer_p, len_p, type_p, backend, short_oid); + if (!error) git_oid_cpy(out_oid, short_oid); - - return error; } else { struct git_pack_entry e; git_rawobj raw; - int error; - - if ((error = pack_entry_find_prefix(&e, (struct pack_backend *)backend, short_oid, len)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to read pack backend"); - if ((error = git_packfile_unpack(&raw, e.p, &e.offset)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to read pack backend"); - - *buffer_p = raw.data; - *len_p = raw.len; - *type_p = raw.type; - git_oid_cpy(out_oid, &e.sha1); + if ((error = pack_entry_find_prefix( + &e, (struct pack_backend *)backend, short_oid, len)) == 0 && + (error = git_packfile_unpack(&raw, e.p, &e.offset)) == 0) + { + *buffer_p = raw.data; + *len_p = raw.len; + *type_p = raw.type; + git_oid_cpy(out_oid, &e.sha1); + } } - return GIT_SUCCESS; + return error; } static int pack_backend__exists(git_odb_backend *backend, const git_oid *oid) { struct git_pack_entry e; - return pack_entry_find(&e, (struct pack_backend *)backend, oid) == GIT_SUCCESS; + return pack_entry_find(&e, (struct pack_backend *)backend, oid) == 0; } static void pack_backend__free(git_odb_backend *_backend) { struct pack_backend *backend; - size_t i; + unsigned int i; assert(_backend); @@ -455,21 +443,18 @@ int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir) { struct pack_backend *backend = NULL; git_buf path = GIT_BUF_INIT; - int error = GIT_SUCCESS; backend = git__calloc(1, sizeof(struct pack_backend)); - if (backend == NULL) - return GIT_ENOMEM; - - error = git_vector_init(&backend->packs, 8, packfile_sort__cb); - if (error < GIT_SUCCESS) - goto cleanup; + GITERR_CHECK_ALLOC(backend); - error = git_buf_joinpath(&path, objects_dir, "pack"); - if (error < GIT_SUCCESS) - goto cleanup; + if (git_vector_init(&backend->packs, 8, packfile_sort__cb) < 0 || + git_buf_joinpath(&path, objects_dir, "pack") < 0) + { + git__free(backend); + return -1; + } - if (git_path_isdir(git_buf_cstr(&path)) == GIT_SUCCESS) { + if (git_path_isdir(git_buf_cstr(&path)) == true) { backend->pack_folder = git_buf_detach(&path); backend->pack_folder_mtime = 0; } @@ -482,10 +467,7 @@ int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir) *backend_out = (git_odb_backend *)backend; -cleanup: - if (error < GIT_SUCCESS) - git__free(backend); git_buf_free(&path); - return error; + return 0; } @@ -13,13 +13,19 @@ static char to_hex[] = "0123456789abcdef"; +static int oid_error_invalid(const char *msg) +{ + giterr_set(GITERR_INVALID, "Unable to parse OID - %s", msg); + return -1; +} + int git_oid_fromstrn(git_oid *out, const char *str, size_t length) { size_t p; int v; if (length < 4) - return git__throw(GIT_ENOTOID, "Failed to generate sha1. Given string is too short"); + return oid_error_invalid("input too short"); if (length > GIT_OID_HEXSZ) length = GIT_OID_HEXSZ; @@ -29,7 +35,7 @@ int git_oid_fromstrn(git_oid *out, const char *str, size_t length) | git__fromhex(str[p + 1]); if (v < 0) - return git__throw(GIT_ENOTOID, "Failed to generate sha1. Given string is not a valid sha1 hash"); + return oid_error_invalid("contains invalid characters"); out->id[p / 2] = (unsigned char)v; } @@ -37,7 +43,7 @@ int git_oid_fromstrn(git_oid *out, const char *str, size_t length) if (length % 2) { v = (git__fromhex(str[p + 0]) << 4); if (v < 0) - return git__throw(GIT_ENOTOID, "Failed to generate sha1. Given string is not a valid sha1 hash"); + return oid_error_invalid("contains invalid characters"); out->id[p / 2] = (unsigned char)v; p += 2; @@ -45,7 +51,7 @@ int git_oid_fromstrn(git_oid *out, const char *str, size_t length) memset(out->id + p / 2, 0, (GIT_OID_HEXSZ - p) / 2); - return GIT_SUCCESS; + return 0; } int git_oid_fromstr(git_oid *out, const char *str) @@ -109,8 +115,9 @@ char *git_oid_tostr(char *out, size_t n, const git_oid *oid) return out; } -int git_oid__parse(git_oid *oid, const char **buffer_out, - const char *buffer_end, const char *header) +int git_oid__parse( + git_oid *oid, const char **buffer_out, + const char *buffer_end, const char *header) { const size_t sha_len = GIT_OID_HEXSZ; const size_t header_len = strlen(header); @@ -118,20 +125,20 @@ int git_oid__parse(git_oid *oid, const char **buffer_out, const char *buffer = *buffer_out; if (buffer + (header_len + sha_len + 1) > buffer_end) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse OID. Buffer too small"); + return -1; if (memcmp(buffer, header, header_len) != 0) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse OID. Buffer and header do not match"); + return -1; if (buffer[header_len + sha_len] != '\n') - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse OID. Buffer not terminated correctly"); + return -1; - if (git_oid_fromstr(oid, buffer + header_len) < GIT_SUCCESS) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse OID. Failed to generate sha1"); + if (git_oid_fromstr(oid, buffer + header_len) < 0) + return -1; *buffer_out = buffer + (header_len + sha_len + 1); - return GIT_SUCCESS; + return 0; } void git_oid__writebuf(git_buf *buf, const char *header, const git_oid *oid) @@ -182,12 +189,11 @@ int git_oid_ncmp(const git_oid *oid_a, const git_oid *oid_b, unsigned int len) int git_oid_streq(const git_oid *a, const char *str) { git_oid id; - int error; - if ((error = git_oid_fromstr(&id, str)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to convert '%s' to oid.", str); + if (git_oid_fromstr(&id, str) < 0) + return -1; - return git_oid_cmp(a, &id) == 0 ? GIT_SUCCESS : GIT_ERROR; + return git_oid_cmp(a, &id) == 0 ? 0 : -1; } int git_oid_iszero(const git_oid *oid_a) @@ -216,15 +222,14 @@ struct git_oid_shorten { static int resize_trie(git_oid_shorten *self, size_t new_size) { self->nodes = git__realloc(self->nodes, new_size * sizeof(trie_node)); - if (self->nodes == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(self->nodes); if (new_size > self->size) { memset(&self->nodes[self->size], 0x0, (new_size - self->size) * sizeof(trie_node)); } self->size = new_size; - return GIT_SUCCESS; + return 0; } static trie_node *push_leaf(git_oid_shorten *os, node_index idx, int push_at, const char *oid) @@ -233,7 +238,7 @@ static trie_node *push_leaf(git_oid_shorten *os, node_index idx, int push_at, co node_index idx_leaf; if (os->node_count >= os->size) { - if (resize_trie(os, os->size * 2) < GIT_SUCCESS) + if (resize_trie(os, os->size * 2) < 0) return NULL; } @@ -255,19 +260,19 @@ git_oid_shorten *git_oid_shorten_new(size_t min_length) { git_oid_shorten *os; - os = git__malloc(sizeof(git_oid_shorten)); + assert((size_t)((int)min_length) == min_length); + + os = git__calloc(1, sizeof(git_oid_shorten)); if (os == NULL) return NULL; - memset(os, 0x0, sizeof(git_oid_shorten)); - - if (resize_trie(os, 16) < GIT_SUCCESS) { + if (resize_trie(os, 16) < 0) { git__free(os); return NULL; } os->node_count = 1; - os->min_length = min_length; + os->min_length = (int)min_length; return os; } @@ -325,24 +330,27 @@ void git_oid_shorten_free(git_oid_shorten *os) */ int git_oid_shorten_add(git_oid_shorten *os, const char *text_oid) { - int i, is_leaf; + int i; + bool is_leaf; node_index idx; if (os->full) - return GIT_ENOMEM; + return -1; if (text_oid == NULL) return os->min_length; idx = 0; - is_leaf = 0; + is_leaf = false; for (i = 0; i < GIT_OID_HEXSZ; ++i) { int c = git__fromhex(text_oid[i]); trie_node *node; - if (c == -1) - return git__throw(GIT_ENOTOID, "Failed to shorten OID. Invalid hex value"); + if (c == -1) { + giterr_set(GITERR_INVALID, "Unable to shorten OID - invalid hex value"); + return -1; + } node = &os->nodes[idx]; @@ -353,22 +361,21 @@ int git_oid_shorten_add(git_oid_shorten *os, const char *text_oid) node->tail = NULL; node = push_leaf(os, idx, git__fromhex(tail[0]), &tail[1]); - if (node == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(node); } if (node->children[c] == 0) { if (push_leaf(os, idx, c, &text_oid[i + 1]) == NULL) - return GIT_ENOMEM; + return -1; break; } idx = node->children[c]; - is_leaf = 0; + is_leaf = false; if (idx < 0) { node->children[c] = idx = -idx; - is_leaf = 1; + is_leaf = true; } } diff --git a/src/oidmap.h b/src/oidmap.h new file mode 100644 index 000000000..858268c92 --- /dev/null +++ b/src/oidmap.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_oidmap_h__ +#define INCLUDE_oidmap_h__ + +#include "common.h" +#include "git2/oid.h" + +#define kmalloc git__malloc +#define kcalloc git__calloc +#define krealloc git__realloc +#define kfree git__free +#include "khash.h" + +__KHASH_TYPE(oid, const git_oid *, void *); +typedef khash_t(oid) git_oidmap; + +GIT_INLINE(khint_t) hash_git_oid(const git_oid *oid) +{ + int i; + khint_t h = 0; + for (i = 0; i < 20; ++i) + h = (h << 5) - h + oid->id[i]; + return h; +} + +GIT_INLINE(int) hash_git_oid_equal(const git_oid *a, const git_oid *b) +{ + return (memcmp(a->id, b->id, sizeof(a->id)) == 0); +} + +#define GIT__USE_OIDMAP \ + __KHASH_IMPL(oid, static inline, const git_oid *, void *, 1, hash_git_oid, hash_git_oid_equal) + +#define git_oidmap_alloc() kh_init(oid) +#define git_oidmap_free(h) kh_destroy(oid,h), h = NULL + +#endif diff --git a/src/pack.c b/src/pack.c index 0d618324b..8d71138a2 100644 --- a/src/pack.c +++ b/src/pack.c @@ -17,12 +17,12 @@ #include <zlib.h> static int packfile_open(struct git_pack_file *p); -static off_t nth_packed_object_offset(const struct git_pack_file *p, uint32_t n); +static git_off_t nth_packed_object_offset(const struct git_pack_file *p, uint32_t n); int packfile_unpack_compressed( git_rawobj *obj, struct git_pack_file *p, git_mwindow **w_curs, - off_t *curpos, + git_off_t *curpos, size_t size, git_otype type); @@ -34,12 +34,18 @@ int packfile_unpack_compressed( * GIT_OID_MINPREFIXLEN and GIT_OID_HEXSZ. */ static int pack_entry_find_offset( - off_t *offset_out, + git_off_t *offset_out, git_oid *found_oid, struct git_pack_file *p, const git_oid *short_oid, unsigned int len); +static int packfile_error(const char *message) +{ + giterr_set(GITERR_ODB, "Invalid pack file - %s", message); + return -1; +} + /*********************************************************** * * PACK INDEX METHODS @@ -58,40 +64,31 @@ static int pack_index_check(const char *path, struct git_pack_file *p) { struct git_pack_idx_header *hdr; uint32_t version, nr, i, *index; - void *idx_map; size_t idx_size; - struct stat st; - - /* TODO: properly open the file without access time */ - git_file fd = p_open(path, O_RDONLY /*| O_NOATIME */); - int error; - + /* TODO: properly open the file without access time using O_NOATIME */ + git_file fd = git_futils_open_ro(path); if (fd < 0) - return git__throw(GIT_EOSERR, "Failed to check index. File missing or corrupted"); + return fd; - if (p_fstat(fd, &st) < GIT_SUCCESS) { + if (p_fstat(fd, &st) < 0 || + !S_ISREG(st.st_mode) || + !git__is_sizet(st.st_size) || + (idx_size = (size_t)st.st_size) < 4 * 256 + 20 + 20) + { p_close(fd); - return git__throw(GIT_EOSERR, "Failed to check index. File appears to be corrupted"); - } - - if (!git__is_sizet(st.st_size)) - return GIT_ENOMEM; - - idx_size = (size_t)st.st_size; - - if (idx_size < 4 * 256 + 20 + 20) { - p_close(fd); - return git__throw(GIT_EOBJCORRUPTED, "Failed to check index. Object is corrupted"); + giterr_set(GITERR_OS, "Failed to check pack index."); + return -1; } error = git_futils_mmap_ro(&p->index_map, fd, 0, idx_size); + p_close(fd); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to check index"); + if (error < 0) + return error; hdr = idx_map = p->index_map.data; @@ -100,7 +97,7 @@ static int pack_index_check(const char *path, struct git_pack_file *p) if (version < 2 || version > 2) { git_futils_mmap_free(&p->index_map); - return git__throw(GIT_EOBJCORRUPTED, "Failed to check index. Unsupported index version"); + return packfile_error("unsupported index version"); } } else @@ -116,7 +113,7 @@ static int pack_index_check(const char *path, struct git_pack_file *p) uint32_t n = ntohl(index[i]); if (n < nr) { git_futils_mmap_free(&p->index_map); - return git__throw(GIT_EOBJCORRUPTED, "Failed to check index. Index is non-monotonic"); + return packfile_error("index is non-monotonic"); } nr = n; } @@ -131,7 +128,7 @@ static int pack_index_check(const char *path, struct git_pack_file *p) */ if (idx_size != 4*256 + nr * 24 + 20 + 20) { git_futils_mmap_free(&p->index_map); - return git__throw(GIT_EOBJCORRUPTED, "Failed to check index. Object is corrupted"); + return packfile_error("index is corrupted"); } } else if (version == 2) { /* @@ -155,39 +152,46 @@ static int pack_index_check(const char *path, struct git_pack_file *p) if (idx_size < min_size || idx_size > max_size) { git_futils_mmap_free(&p->index_map); - return git__throw(GIT_EOBJCORRUPTED, "Failed to check index. Wrong index size"); + return packfile_error("wrong index size"); } } p->index_version = version; p->num_objects = nr; - return GIT_SUCCESS; + return 0; } static int pack_index_open(struct git_pack_file *p) { char *idx_name; int error; + size_t name_len, offset; if (p->index_map.data) - return GIT_SUCCESS; + return 0; idx_name = git__strdup(p->pack_name); - strcpy(idx_name + strlen(idx_name) - strlen(".pack"), ".idx"); + GITERR_CHECK_ALLOC(idx_name); + + name_len = strlen(idx_name); + offset = name_len - strlen(".pack"); + assert(offset < name_len); /* make sure no underflow */ + + strncpy(idx_name + offset, ".idx", name_len - offset); error = pack_index_check(idx_name, p); git__free(idx_name); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to open index"); + return error; } static unsigned char *pack_window_open( struct git_pack_file *p, git_mwindow **w_cursor, - off_t offset, + git_off_t offset, unsigned int *left) { - if (p->mwf.fd == -1 && packfile_open(p) < GIT_SUCCESS) + if (p->mwf.fd == -1 && packfile_open(p) < 0) return NULL; /* Since packfiles end in a hash of their content and it's @@ -201,7 +205,8 @@ static unsigned char *pack_window_open( return git_mwindow_open(&p->mwf, w_cursor, offset, 20, left); } -static unsigned long packfile_unpack_header1( +static int packfile_unpack_header1( + unsigned long *usedp, size_t *sizep, git_otype *type, const unsigned char *buf, @@ -216,8 +221,13 @@ static unsigned long packfile_unpack_header1( size = c & 15; shift = 4; while (c & 0x80) { - if (len <= used || bitsizeof(long) <= shift) - return 0; + if (len <= used) + return GIT_ESHORTBUFFER; + + if (bitsizeof(long) <= shift) { + *usedp = 0; + return -1; + } c = buf[used++]; size += (c & 0x7f) << shift; @@ -225,7 +235,8 @@ static unsigned long packfile_unpack_header1( } *sizep = (size_t)size; - return used; + *usedp = used; + return 0; } int git_packfile_unpack_header( @@ -233,11 +244,12 @@ int git_packfile_unpack_header( git_otype *type_p, git_mwindow_file *mwf, git_mwindow **w_curs, - off_t *curpos) + git_off_t *curpos) { unsigned char *base; unsigned int left; unsigned long used; + int ret; /* pack_window_open() assures us we have [base, base + 20) available * as a range that we can look at at. (Its actually the hash @@ -248,37 +260,39 @@ int git_packfile_unpack_header( // base = pack_window_open(p, w_curs, *curpos, &left); base = git_mwindow_open(mwf, w_curs, *curpos, 20, &left); if (base == NULL) - return GIT_ENOMEM; - - used = packfile_unpack_header1(size_p, type_p, base, left); + return GIT_ESHORTBUFFER; - if (used == 0) - return git__throw(GIT_EOBJCORRUPTED, "Header length is zero"); + ret = packfile_unpack_header1(&used, size_p, type_p, base, left); + git_mwindow_close(w_curs); + if (ret == GIT_ESHORTBUFFER) + return ret; + else if (ret < 0) + return packfile_error("header length is zero"); *curpos += used; - return GIT_SUCCESS; + return 0; } static int packfile_unpack_delta( git_rawobj *obj, struct git_pack_file *p, git_mwindow **w_curs, - off_t *curpos, + git_off_t *curpos, size_t delta_size, git_otype delta_type, - off_t obj_offset) + git_off_t obj_offset) { - off_t base_offset; + git_off_t base_offset; git_rawobj base, delta; int error; base_offset = get_delta_base(p, w_curs, curpos, delta_type, obj_offset); + git_mwindow_close(w_curs); if (base_offset == 0) - return git__throw(GIT_EOBJCORRUPTED, "Delta offset is zero"); - if (base_offset < 0) - return git__rethrow(base_offset, "Failed to get delta base"); + return packfile_error("delta offset is zero"); + if (base_offset < 0) /* must actually be an error code */ + return (int)base_offset; - git_mwindow_close(w_curs); error = git_packfile_unpack(&base, p, &base_offset); /* @@ -287,35 +301,35 @@ static int packfile_unpack_delta( * * We'll need to do this in order to support thin packs. */ - if (error < GIT_SUCCESS) - return git__rethrow(error, "Corrupted delta"); + if (error < 0) + return error; error = packfile_unpack_compressed(&delta, p, w_curs, curpos, delta_size, delta_type); - if (error < GIT_SUCCESS) { + git_mwindow_close(w_curs); + if (error < 0) { git__free(base.data); - return git__rethrow(error, "Corrupted delta"); + return error; } obj->type = base.type; - error = git__delta_apply(obj, - base.data, base.len, - delta.data, delta.len); + error = git__delta_apply(obj, base.data, base.len, delta.data, delta.len); git__free(base.data); git__free(delta.data); /* TODO: we might want to cache this shit. eventually */ //add_delta_base_cache(p, base_offset, base, base_size, *type); + return error; /* error set by git__delta_apply */ } int git_packfile_unpack( - git_rawobj *obj, - struct git_pack_file *p, - off_t *obj_offset) + git_rawobj *obj, + struct git_pack_file *p, + git_off_t *obj_offset) { git_mwindow *w_curs = NULL; - off_t curpos = *obj_offset; + git_off_t curpos = *obj_offset; int error; size_t size = 0; @@ -330,8 +344,10 @@ int git_packfile_unpack( obj->type = GIT_OBJ_BAD; error = git_packfile_unpack_header(&size, &type, &p->mwf, &w_curs, &curpos); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to unpack packfile"); + git_mwindow_close(&w_curs); + + if (error < 0) + return error; switch (type) { case GIT_OBJ_OFS_DELTA: @@ -351,33 +367,28 @@ int git_packfile_unpack( break; default: - error = GIT_EOBJCORRUPTED; + error = packfile_error("invalid packfile type in header");; break; } - git_mwindow_close(&w_curs); - - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to unpack object"); - *obj_offset = curpos; - return GIT_SUCCESS; + return error; } int packfile_unpack_compressed( - git_rawobj *obj, - struct git_pack_file *p, - git_mwindow **w_curs, - off_t *curpos, - size_t size, - git_otype type) + git_rawobj *obj, + struct git_pack_file *p, + git_mwindow **w_curs, + git_off_t *curpos, + size_t size, + git_otype type) { int st; z_stream stream; unsigned char *buffer, *in; - buffer = git__malloc(size + 1); - memset(buffer, 0x0, size + 1); + buffer = git__calloc(1, size + 1); + GITERR_CHECK_ALLOC(buffer); memset(&stream, 0, sizeof(stream)); stream.next_out = buffer; @@ -386,17 +397,26 @@ int packfile_unpack_compressed( st = inflateInit(&stream); if (st != Z_OK) { git__free(buffer); - return git__throw(GIT_EZLIB, "Error in zlib"); + giterr_set(GITERR_ZLIB, "Failed to inflate packfile"); + + return -1; } do { in = pack_window_open(p, w_curs, *curpos, &stream.avail_in); stream.next_in = in; st = inflate(&stream, Z_FINISH); + git_mwindow_close(w_curs); if (!stream.avail_out) break; /* the payload is larger than it should be */ + if (st == Z_BUF_ERROR && in == NULL) { + inflateEnd(&stream); + git__free(buffer); + return GIT_ESHORTBUFFER; + } + *curpos += stream.next_in - in; } while (st == Z_OK || st == Z_BUF_ERROR); @@ -404,30 +424,36 @@ int packfile_unpack_compressed( if ((st != Z_STREAM_END) || stream.total_out != size) { git__free(buffer); - return git__throw(GIT_EZLIB, "Error in zlib"); + giterr_set(GITERR_ZLIB, "Failed to inflate packfile"); + return -1; } obj->type = type; obj->len = size; obj->data = buffer; - return GIT_SUCCESS; + return 0; } /* * curpos is where the data starts, delta_obj_offset is the where the * header starts */ -off_t get_delta_base( - struct git_pack_file *p, - git_mwindow **w_curs, - off_t *curpos, - git_otype type, - off_t delta_obj_offset) +git_off_t get_delta_base( + struct git_pack_file *p, + git_mwindow **w_curs, + git_off_t *curpos, + git_otype type, + git_off_t delta_obj_offset) { - unsigned char *base_info = pack_window_open(p, w_curs, *curpos, NULL); - off_t base_offset; + unsigned int left = 0; + unsigned char *base_info; + git_off_t base_offset; git_oid unused; + base_info = pack_window_open(p, w_curs, *curpos, &left); + /* Assumption: the only reason this would fail is because the file is too small */ + if (base_info == NULL) + return GIT_ESHORTBUFFER; /* pack_window_open() assured us we have [base_info, base_info + 20) * as a range that we can look at without walking off the * end of the mapped window. Its actually the hash size @@ -439,6 +465,8 @@ off_t get_delta_base( unsigned char c = base_info[used++]; base_offset = c & 127; while (c & 128) { + if (left <= used) + return GIT_ESHORTBUFFER; base_offset += 1; if (!base_offset || MSB(base_offset, 7)) return 0; /* overflow */ @@ -463,8 +491,8 @@ off_t get_delta_base( } } /* The base entry _must_ be in the same pack */ - if (pack_entry_find_offset(&base_offset, &unused, p, (git_oid *)base_info, GIT_OID_HEXSZ) < GIT_SUCCESS) - return git__rethrow(GIT_EPACKCORRUPTED, "Base entry delta is not in the same pack"); + if (pack_entry_find_offset(&base_offset, &unused, p, (git_oid *)base_info, GIT_OID_HEXSZ) < 0) + return packfile_error("base entry delta is not in the same pack"); *curpos += 20; } else return 0; @@ -478,11 +506,11 @@ off_t get_delta_base( * ***********************************************************/ -static struct git_pack_file *packfile_alloc(int extra) +static struct git_pack_file *packfile_alloc(size_t extra) { - struct git_pack_file *p = git__malloc(sizeof(*p) + extra); - memset(p, 0, sizeof(*p)); - p->mwf.fd = -1; + struct git_pack_file *p = git__calloc(1, sizeof(*p) + extra); + if (p != NULL) + p->mwf.fd = -1; return p; } @@ -510,24 +538,25 @@ static int packfile_open(struct git_pack_file *p) git_oid sha1; unsigned char *idx_sha1; - if (!p->index_map.data && pack_index_open(p) < GIT_SUCCESS) - return git__throw(GIT_ENOTFOUND, "Failed to open packfile. File not found"); + assert(p->index_map.data); + + if (!p->index_map.data && pack_index_open(p) < 0) + return git_odb__error_notfound("failed to open packfile"); /* TODO: open with noatime */ - p->mwf.fd = p_open(p->pack_name, O_RDONLY); - if (p->mwf.fd < 0 || p_fstat(p->mwf.fd, &st) < GIT_SUCCESS) - return git__throw(GIT_EOSERR, "Failed to open packfile. File appears to be corrupted"); + p->mwf.fd = git_futils_open_ro(p->pack_name); + if (p->mwf.fd < 0) + return p->mwf.fd; - if (git_mwindow_file_register(&p->mwf) < GIT_SUCCESS) { - p_close(p->mwf.fd); - return git__throw(GIT_ERROR, "Failed to register packfile windows"); - } + if (p_fstat(p->mwf.fd, &st) < 0 || + git_mwindow_file_register(&p->mwf) < 0) + goto cleanup; /* If we created the struct before we had the pack we lack size. */ if (!p->mwf.size) { if (!S_ISREG(st.st_mode)) goto cleanup; - p->mwf.size = (off_t)st.st_size; + p->mwf.size = (git_off_t)st.st_size; } else if (p->mwf.size != st.st_size) goto cleanup; @@ -537,44 +566,35 @@ static int packfile_open(struct git_pack_file *p) */ fd_flag = fcntl(p->mwf.fd, F_GETFD, 0); if (fd_flag < 0) - return error("cannot determine file descriptor flags"); + goto cleanup; fd_flag |= FD_CLOEXEC; if (fcntl(p->pack_fd, F_SETFD, fd_flag) == -1) - return GIT_EOSERR; + goto cleanup; #endif /* Verify we recognize this pack file format. */ - if (p_read(p->mwf.fd, &hdr, sizeof(hdr)) < GIT_SUCCESS) - goto cleanup; - - if (hdr.hdr_signature != htonl(PACK_SIGNATURE)) - goto cleanup; - - if (!pack_version_ok(hdr.hdr_version)) + if (p_read(p->mwf.fd, &hdr, sizeof(hdr)) < 0 || + hdr.hdr_signature != htonl(PACK_SIGNATURE) || + !pack_version_ok(hdr.hdr_version)) goto cleanup; /* Verify the pack matches its index. */ - if (p->num_objects != ntohl(hdr.hdr_entries)) - goto cleanup; - - if (p_lseek(p->mwf.fd, p->mwf.size - GIT_OID_RAWSZ, SEEK_SET) == -1) - goto cleanup; - - if (p_read(p->mwf.fd, sha1.id, GIT_OID_RAWSZ) < GIT_SUCCESS) + if (p->num_objects != ntohl(hdr.hdr_entries) || + p_lseek(p->mwf.fd, p->mwf.size - GIT_OID_RAWSZ, SEEK_SET) == -1 || + p_read(p->mwf.fd, sha1.id, GIT_OID_RAWSZ) < 0) goto cleanup; idx_sha1 = ((unsigned char *)p->index_map.data) + p->index_map.len - 40; - if (git_oid_cmp(&sha1, (git_oid *)idx_sha1) != 0) - goto cleanup; - - return GIT_SUCCESS; + if (git_oid_cmp(&sha1, (git_oid *)idx_sha1) == 0) + return 0; cleanup: + giterr_set(GITERR_OS, "Invalid packfile '%s'", p->pack_name); p_close(p->mwf.fd); p->mwf.fd = -1; - return git__throw(GIT_EPACKCORRUPTED, "Failed to open packfile. Pack is corrupted"); + return -1; } int git_packfile_check(struct git_pack_file **pack_out, const char *path) @@ -586,6 +606,7 @@ int git_packfile_check(struct git_pack_file **pack_out, const char *path) *pack_out = NULL; path_len = strlen(path); p = packfile_alloc(path_len + 2); + GITERR_CHECK_ALLOC(p); /* * Make sure a corresponding .pack file exists and that @@ -594,19 +615,19 @@ int git_packfile_check(struct git_pack_file **pack_out, const char *path) path_len -= strlen(".idx"); if (path_len < 1) { git__free(p); - return git__throw(GIT_ENOTFOUND, "Failed to check packfile. Wrong path name"); + return git_odb__error_notfound("invalid packfile path"); } memcpy(p->pack_name, path, path_len); strcpy(p->pack_name + path_len, ".keep"); - if (git_path_exists(p->pack_name) == GIT_SUCCESS) + if (git_path_exists(p->pack_name) == true) p->pack_keep = 1; strcpy(p->pack_name + path_len, ".pack"); - if (p_stat(p->pack_name, &st) < GIT_SUCCESS || !S_ISREG(st.st_mode)) { + if (p_stat(p->pack_name, &st) < 0 || !S_ISREG(st.st_mode)) { git__free(p); - return git__throw(GIT_ENOTFOUND, "Failed to check packfile. File not found"); + return git_odb__error_notfound("packfile not found"); } /* ok, it looks sane as far as we can check without @@ -618,11 +639,12 @@ int git_packfile_check(struct git_pack_file **pack_out, const char *path) /* see if we can parse the sha1 oid in the packfile name */ if (path_len < 40 || - git_oid_fromstr(&p->sha1, path + path_len - GIT_OID_HEXSZ) < GIT_SUCCESS) + git_oid_fromstr(&p->sha1, path + path_len - GIT_OID_HEXSZ) < 0) memset(&p->sha1, 0x0, GIT_OID_RAWSZ); *pack_out = p; - return GIT_SUCCESS; + + return 0; } /*********************************************************** @@ -631,7 +653,7 @@ int git_packfile_check(struct git_pack_file **pack_out, const char *path) * ***********************************************************/ -static off_t nth_packed_object_offset(const struct git_pack_file *p, uint32_t n) +static git_off_t nth_packed_object_offset(const struct git_pack_file *p, uint32_t n) { const unsigned char *index = p->index_map.data; index += 4 * 256; @@ -650,11 +672,11 @@ static off_t nth_packed_object_offset(const struct git_pack_file *p, uint32_t n) } static int pack_entry_find_offset( - off_t *offset_out, - git_oid *found_oid, - struct git_pack_file *p, - const git_oid *short_oid, - unsigned int len) + git_off_t *offset_out, + git_oid *found_oid, + struct git_pack_file *p, + const git_oid *short_oid, + unsigned int len) { const uint32_t *level1_ofs = p->index_map.data; const unsigned char *index = p->index_map.data; @@ -667,8 +689,8 @@ static int pack_entry_find_offset( if (index == NULL) { int error; - if ((error = pack_index_open(p)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to find offset for pack entry"); + if ((error = pack_index_open(p)) < 0) + return error; assert(p->index_map.data); @@ -726,22 +748,22 @@ static int pack_entry_find_offset( } } - if (!found) { - return git__throw(GIT_ENOTFOUND, "Failed to find offset for pack entry. Entry not found"); - } else if (found > 1) { - return git__throw(GIT_EAMBIGUOUSOIDPREFIX, "Failed to find offset for pack entry. Ambiguous sha1 prefix within pack"); - } else { - *offset_out = nth_packed_object_offset(p, pos); - git_oid_fromraw(found_oid, current); + if (!found) + return git_odb__error_notfound("failed to find offset for pack entry"); + if (found > 1) + return git_odb__error_ambiguous("found multiple offsets for pack entry"); + *offset_out = nth_packed_object_offset(p, pos); + git_oid_fromraw(found_oid, current); #ifdef INDEX_DEBUG_LOOKUP + { unsigned char hex_sha1[GIT_OID_HEXSZ + 1]; git_oid_fmt(hex_sha1, found_oid); hex_sha1[GIT_OID_HEXSZ] = '\0'; printf("found lo=%d %s\n", lo, hex_sha1); -#endif - return GIT_SUCCESS; } +#endif + return 0; } int git_pack_entry_find( @@ -750,7 +772,7 @@ int git_pack_entry_find( const git_oid *short_oid, unsigned int len) { - off_t offset; + git_off_t offset; git_oid found_oid; int error; @@ -760,22 +782,22 @@ int git_pack_entry_find( unsigned i; for (i = 0; i < p->num_bad_objects; i++) if (git_oid_cmp(short_oid, &p->bad_object_sha1[i]) == 0) - return git__throw(GIT_ERROR, "Failed to find pack entry. Bad object found"); + return packfile_error("bad object found in packfile"); } error = pack_entry_find_offset(&offset, &found_oid, p, short_oid, len); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to find pack entry. Couldn't find offset"); + if (error < 0) + return error; /* we found a unique entry in the index; * make sure the packfile backing the index * still exists on disk */ - if (p->mwf.fd == -1 && packfile_open(p) < GIT_SUCCESS) - return git__throw(GIT_EOSERR, "Failed to find pack entry. Packfile doesn't exist on disk"); + if (p->mwf.fd == -1 && (error = packfile_open(p)) < 0) + return error; e->offset = offset; e->p = p; git_oid_cpy(&e->sha1, &found_oid); - return GIT_SUCCESS; + return 0; } diff --git a/src/pack.h b/src/pack.h index 590297847..cd7a4d2e1 100644 --- a/src/pack.h +++ b/src/pack.h @@ -70,7 +70,7 @@ struct git_pack_file { }; struct git_pack_entry { - off_t offset; + git_off_t offset; git_oid sha1; struct git_pack_file *p; }; @@ -80,13 +80,20 @@ int git_packfile_unpack_header( git_otype *type_p, git_mwindow_file *mwf, git_mwindow **w_curs, - off_t *curpos); - -int git_packfile_unpack(git_rawobj *obj, struct git_pack_file *p, off_t *obj_offset); - -off_t get_delta_base(struct git_pack_file *p, git_mwindow **w_curs, - off_t *curpos, git_otype type, - off_t delta_obj_offset); + git_off_t *curpos); + +int git_packfile_unpack(git_rawobj *obj, struct git_pack_file *p, git_off_t *obj_offset); +int packfile_unpack_compressed( + git_rawobj *obj, + struct git_pack_file *p, + git_mwindow **w_curs, + git_off_t *curpos, + size_t size, + git_otype type); + +git_off_t get_delta_base(struct git_pack_file *p, git_mwindow **w_curs, + git_off_t *curpos, git_otype type, + git_off_t delta_obj_offset); void packfile_free(struct git_pack_file *p); int git_packfile_check(struct git_pack_file **pack_out, const char *path); diff --git a/src/path.c b/src/path.c index 2e6a1eb40..703f43af1 100644 --- a/src/path.c +++ b/src/path.c @@ -50,16 +50,14 @@ int git_path_basename_r(git_buf *buffer, const char *path) while (startp > path && *(startp - 1) != '/') startp--; - len = endp - startp +1; + /* Cast is safe because max path < max int */ + len = (int)(endp - startp + 1); Exit: result = len; - if (buffer != NULL) { - if (git_buf_set(buffer, startp, len) < GIT_SUCCESS) - return git__rethrow(git_buf_lasterror(buffer), - "Could not get basename of '%s'", path); - } + if (buffer != NULL && git_buf_set(buffer, startp, len) < 0) + return -1; return result; } @@ -100,7 +98,8 @@ int git_path_dirname_r(git_buf *buffer, const char *path) endp--; } while (endp > path && *endp == '/'); - len = endp - path +1; + /* Cast is safe because max path < max int */ + len = (int)(endp - path + 1); #ifdef GIT_WIN32 /* Mimic unix behavior where '/.git' returns '/': 'C:/.git' will return @@ -115,11 +114,8 @@ int git_path_dirname_r(git_buf *buffer, const char *path) Exit: result = len; - if (buffer != NULL) { - if (git_buf_set(buffer, path, len) < GIT_SUCCESS) - return git__rethrow(git_buf_lasterror(buffer), - "Could not get dirname of '%s'", path); - } + if (buffer != NULL && git_buf_set(buffer, path, len) < 0) + return -1; return result; } @@ -153,7 +149,7 @@ char *git_path_basename(const char *path) const char *git_path_topdir(const char *path) { size_t len; - int i; + ssize_t i; assert(path); len = strlen(path); @@ -161,7 +157,7 @@ const char *git_path_topdir(const char *path) if (!len || path[len - 1] != '/') return NULL; - for (i = len - 2; i >= 0; --i) + for (i = (ssize_t)len - 2; i >= 0; --i) if (path[i] == '/') break; @@ -176,54 +172,61 @@ int git_path_root(const char *path) /* Does the root of the path look like a windows drive ? */ if (isalpha(path[0]) && (path[1] == ':')) offset += 2; + + /* Are we dealing with a windows network path? */ + else if ((path[0] == '/' && path[1] == '/') || + (path[0] == '\\' && path[1] == '\\')) + { + offset += 2; + + /* Skip the computer name segment */ + while (path[offset] && path[offset] != '/' && path[offset] != '\\') + offset++; + } #endif - if (*(path + offset) == '/') + if (path[offset] == '/' || path[offset] == '\\') return offset; - return -1; /* Not a real error. Rather a signal than the path is not rooted */ + return -1; /* Not a real error - signals that path is not rooted */ } int git_path_prettify(git_buf *path_out, const char *path, const char *base) { - int error = GIT_SUCCESS; char buf[GIT_PATH_MAX]; - git_buf_clear(path_out); + assert(path && path_out); /* construct path if needed */ if (base != NULL && git_path_root(path) < 0) { - if ((error = git_buf_joinpath(path_out, base, path)) < GIT_SUCCESS) - return error; + if (git_buf_joinpath(path_out, base, path) < 0) + return -1; path = path_out->ptr; } - if (path == NULL || p_realpath(path, buf) == NULL) - error = GIT_EOSERR; - else - error = git_buf_sets(path_out, buf); + if (p_realpath(path, buf) == NULL) { + giterr_set(GITERR_OS, "Failed to resolve path '%s'", path); + git_buf_clear(path_out); + return (errno == ENOENT) ? GIT_ENOTFOUND : -1; + } - return error; + return git_buf_sets(path_out, buf); } int git_path_prettify_dir(git_buf *path_out, const char *path, const char *base) { int error = git_path_prettify(path_out, path, base); - - if (error == GIT_SUCCESS) - error = git_path_to_dir(path_out); - - return error; + return (error < 0) ? error : git_path_to_dir(path_out); } int git_path_to_dir(git_buf *path) { if (path->asize > 0 && - path->size > 0 && - path->ptr[path->size - 1] != '/') + git_buf_len(path) > 0 && + path->ptr[git_buf_len(path) - 1] != '/') git_buf_putc(path, '/'); - return git_buf_lasterror(path); + return git_buf_oom(path) ? -1 : 0; } void git_path_string_to_dir(char* path, size_t size) @@ -238,10 +241,10 @@ void git_path_string_to_dir(char* path, size_t size) int git__percent_decode(git_buf *decoded_out, const char *input) { - int len, hi, lo, i, error = GIT_SUCCESS; + int len, hi, lo, i; assert(decoded_out && input); - len = strlen(input); + len = (int)strlen(input); git_buf_clear(decoded_out); for(i = 0; i < len; i++) @@ -264,39 +267,40 @@ int git__percent_decode(git_buf *decoded_out, const char *input) i += 2; append: - error = git_buf_putc(decoded_out, c); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to percent decode '%s'.", input); + if (git_buf_putc(decoded_out, c) < 0) + return -1; } - return error; + return 0; +} + +static int error_invalid_local_file_uri(const char *uri) +{ + giterr_set(GITERR_CONFIG, "'%s' is not a valid local file URI", uri); + return -1; } int git_path_fromurl(git_buf *local_path_out, const char *file_url) { - int error = GIT_SUCCESS, offset = 0, len; + int offset = 0, len; assert(local_path_out && file_url); if (git__prefixcmp(file_url, "file://") != 0) - return git__throw(GIT_EINVALIDPATH, - "Parsing of '%s' failed. A file Uri is expected (ie. with 'file://' scheme).", - file_url); + return error_invalid_local_file_uri(file_url); offset += 7; - len = strlen(file_url); + len = (int)strlen(file_url); if (offset < len && file_url[offset] == '/') offset++; else if (offset < len && git__prefixcmp(file_url + offset, "localhost/") == 0) offset += 10; else - return git__throw(GIT_EINVALIDPATH, - "Parsing of '%s' failed. A local file Uri is expected.", file_url); + return error_invalid_local_file_uri(file_url); if (offset >= len || file_url[offset] == '/') - return git__throw(GIT_EINVALIDPATH, - "Parsing of '%s' failed. Invalid file Uri format.", file_url); + return error_invalid_local_file_uri(file_url); #ifndef _MSC_VER offset--; /* A *nix absolute path starts with a forward slash */ @@ -304,11 +308,7 @@ int git_path_fromurl(git_buf *local_path_out, const char *file_url) git_buf_clear(local_path_out); - error = git__percent_decode(local_path_out, file_url + offset); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Parsing of '%s' failed.", file_url); - - return error; + return git__percent_decode(local_path_out, file_url + offset); } int git_path_walk_up( @@ -317,7 +317,7 @@ int git_path_walk_up( int (*cb)(void *data, git_buf *), void *data) { - int error = GIT_SUCCESS; + int error = 0; git_buf iter; ssize_t stop = 0, scan; char oldc = '\0'; @@ -325,19 +325,19 @@ int git_path_walk_up( assert(path && cb); if (ceiling != NULL) { - if (git__prefixcmp(path->ptr, ceiling) == GIT_SUCCESS) + if (git__prefixcmp(path->ptr, ceiling) == 0) stop = (ssize_t)strlen(ceiling); else - stop = path->size; + stop = git_buf_len(path); } - scan = path->size; + scan = git_buf_len(path); iter.ptr = path->ptr; - iter.size = path->size; + iter.size = git_buf_len(path); iter.asize = path->asize; while (scan >= stop) { - if ((error = cb(data, &iter)) < GIT_SUCCESS) + if ((error = cb(data, &iter)) < 0) break; iter.ptr[scan] = oldc; scan = git_buf_rfind_next(&iter, '/'); @@ -355,116 +355,123 @@ int git_path_walk_up( return error; } -int git_path_exists(const char *path) +bool git_path_exists(const char *path) { assert(path); - return p_access(path, F_OK); + return p_access(path, F_OK) == 0; } -int git_path_isdir(const char *path) +bool git_path_isdir(const char *path) { struct stat st; - if (p_stat(path, &st) < GIT_SUCCESS) - return GIT_ERROR; + if (p_stat(path, &st) < 0) + return false; - return S_ISDIR(st.st_mode) ? GIT_SUCCESS : GIT_ERROR; + return S_ISDIR(st.st_mode) != 0; } -int git_path_isfile(const char *path) +bool git_path_isfile(const char *path) { struct stat st; - int stat_error; assert(path); - stat_error = p_stat(path, &st); + if (p_stat(path, &st) < 0) + return false; - if (stat_error < GIT_SUCCESS) - return -1; + return S_ISREG(st.st_mode) != 0; +} - if (!S_ISREG(st.st_mode)) - return -1; +int git_path_lstat(const char *path, struct stat *st) +{ + int err = 0; - return 0; + if (p_lstat(path, st) < 0) { + err = (errno == ENOENT) ? GIT_ENOTFOUND : -1; + giterr_set(GITERR_OS, "Failed to stat file '%s'", path); + } + + return err; } -static int _check_dir_contents( +static bool _check_dir_contents( git_buf *dir, const char *sub, - int (*predicate)(const char *)) + bool (*predicate)(const char *)) { - int error = GIT_SUCCESS; - size_t dir_size = dir->size; + bool result; + size_t dir_size = git_buf_len(dir); size_t sub_size = strlen(sub); - /* separate allocation and join, so we can always leave git_buf valid */ - if ((error = git_buf_try_grow(dir, dir_size + sub_size + 2)) < GIT_SUCCESS) - return error; + /* leave base valid even if we could not make space for subdir */ + if (git_buf_try_grow(dir, dir_size + sub_size + 2) < 0) + return false; + + /* save excursion */ git_buf_joinpath(dir, dir->ptr, sub); - error = (*predicate)(dir->ptr); + result = predicate(dir->ptr); /* restore path */ git_buf_truncate(dir, dir_size); - - return error; + return result; } -int git_path_contains(git_buf *dir, const char *item) +bool git_path_contains(git_buf *dir, const char *item) { return _check_dir_contents(dir, item, &git_path_exists); } -int git_path_contains_dir(git_buf *base, const char *subdir) +bool git_path_contains_dir(git_buf *base, const char *subdir) { return _check_dir_contents(base, subdir, &git_path_isdir); } -int git_path_contains_file(git_buf *base, const char *file) +bool git_path_contains_file(git_buf *base, const char *file) { return _check_dir_contents(base, file, &git_path_isfile); } int git_path_find_dir(git_buf *dir, const char *path, const char *base) { - int error = GIT_SUCCESS; + int error; if (base != NULL && git_path_root(path) < 0) error = git_buf_joinpath(dir, base, path); else error = git_buf_sets(dir, path); - if (error == GIT_SUCCESS) { + if (!error) { char buf[GIT_PATH_MAX]; if (p_realpath(dir->ptr, buf) != NULL) error = git_buf_sets(dir, buf); } /* call dirname if this is not a directory */ - if (error == GIT_SUCCESS && git_path_isdir(dir->ptr) != GIT_SUCCESS) - if (git_path_dirname_r(dir, dir->ptr) < GIT_SUCCESS) - error = git_buf_lasterror(dir); + if (!error && git_path_isdir(dir->ptr) == false) + error = git_path_dirname_r(dir, dir->ptr); - if (error == GIT_SUCCESS) + if (!error) error = git_path_to_dir(dir); return error; } -int git_path_cmp(const char *name1, int len1, int isdir1, - const char *name2, int len2, int isdir2) +int git_path_cmp( + const char *name1, size_t len1, int isdir1, + const char *name2, size_t len2, int isdir2) { - int len = len1 < len2 ? len1 : len2; + size_t len = len1 < len2 ? len1 : len2; int cmp; cmp = memcmp(name1, name2, len); if (cmp) return cmp; if (len1 < len2) - return ((!isdir1 && !isdir2) ? -1 : - (isdir1 ? '/' - name2[len1] : name2[len1] - '/')); + return (!isdir1 && !isdir2) ? -1 : + (isdir1 ? '/' - name2[len1] : name2[len1] - '/'); if (len1 > len2) - return ((!isdir1 && !isdir2) ? 1 : - (isdir2 ? name1[len2] - '/' : '/' - name1[len2])); + return (!isdir1 && !isdir2) ? 1 : + (isdir2 ? name1[len2] - '/' : '/' - name1[len2]); return 0; } @@ -485,13 +492,15 @@ int git_path_direach( DIR *dir; struct dirent de_buf, *de; - if (git_path_to_dir(path) < GIT_SUCCESS) - return git_buf_lasterror(path); + if (git_path_to_dir(path) < 0) + return -1; + + wd_len = git_buf_len(path); - wd_len = path->size; - dir = opendir(path->ptr); - if (!dir) - return git__throw(GIT_EOSERR, "Failed to process `%s` tree structure. An error occured while opening the directory", path->ptr); + if ((dir = opendir(path->ptr)) == NULL) { + giterr_set(GITERR_OS, "Failed to open directory '%s'", path->ptr); + return -1; + } while (p_readdir_r(dir, &de_buf, &de) == 0 && de != NULL) { int result; @@ -499,21 +508,21 @@ int git_path_direach( if (is_dot_or_dotdot(de->d_name)) continue; - if (git_buf_puts(path, de->d_name) < GIT_SUCCESS) - return git_buf_lasterror(path); + if (git_buf_puts(path, de->d_name) < 0) + return -1; result = fn(arg, path); git_buf_truncate(path, wd_len); /* restore path */ - if (result != GIT_SUCCESS) { + if (result < 0) { closedir(dir); - return result; /* The callee is reponsible for setting the correct error message */ + return -1; } } closedir(dir); - return GIT_SUCCESS; + return 0; } int git_path_dirload( @@ -531,9 +540,10 @@ int git_path_dirload( path_len = strlen(path); assert(path_len > 0 && path_len >= prefix_len); - if ((dir = opendir(path)) == NULL) - return git__throw(GIT_EOSERR, "Failed to process `%s` tree structure." - " An error occured while opening the directory", path); + if ((dir = opendir(path)) == NULL) { + giterr_set(GITERR_OS, "Failed to open directory '%s'", path); + return -1; + } path += prefix_len; path_len -= prefix_len; @@ -550,8 +560,7 @@ int git_path_dirload( entry_path = git__malloc( path_len + need_slash + entry_len + 1 + alloc_extra); - if (entry_path == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(entry_path); if (path_len) memcpy(entry_path, path, path_len); @@ -560,19 +569,16 @@ int git_path_dirload( memcpy(&entry_path[path_len + need_slash], de->d_name, entry_len); entry_path[path_len + need_slash + entry_len] = '\0'; - if ((error = git_vector_insert(contents, entry_path)) < GIT_SUCCESS) { - git__free(entry_path); - return error; - } + if (git_vector_insert(contents, entry_path) < 0) + return -1; } closedir(dir); - if (error != GIT_SUCCESS) - return git__throw( - GIT_EOSERR, "Failed to process directory entry in `%s`", path); + if (error != 0) + giterr_set(GITERR_OS, "Failed to process directory entry in '%s'", path); - return GIT_SUCCESS; + return error; } int git_path_with_stat_cmp(const void *a, const void *b) @@ -591,11 +597,12 @@ int git_path_dirload_with_stat( git_path_with_stat *ps; git_buf full = GIT_BUF_INIT; - if ((error = git_buf_set(&full, path, prefix_len)) != GIT_SUCCESS) - return error; + if (git_buf_set(&full, path, prefix_len) < 0) + return -1; - if ((error = git_path_dirload(path, prefix_len, - sizeof(git_path_with_stat) + 1, contents)) != GIT_SUCCESS) { + error = git_path_dirload( + path, prefix_len, sizeof(git_path_with_stat) + 1, contents); + if (error < 0) { git_buf_free(&full); return error; } @@ -606,8 +613,10 @@ int git_path_dirload_with_stat( memmove(ps->path, ps, path_len + 1); ps->path_len = path_len; - git_buf_joinpath(&full, full.ptr, ps->path); - p_lstat(full.ptr, &ps->st); + if ((error = git_buf_joinpath(&full, full.ptr, ps->path)) < 0 || + (error = git_path_lstat(full.ptr, &ps->st)) < 0) + break; + git_buf_truncate(&full, prefix_len); if (S_ISDIR(ps->st.st_mode)) { diff --git a/src/path.h b/src/path.h index 981fdd6a4..fd76805e5 100644 --- a/src/path.h +++ b/src/path.h @@ -113,48 +113,55 @@ extern int git_path_fromurl(git_buf *local_path_out, const char *file_url); /** * Check if a file exists and can be accessed. - * @return GIT_SUCCESS if file exists, < 0 otherwise. + * @return true or false */ -extern int git_path_exists(const char *path); +extern bool git_path_exists(const char *path); /** * Check if the given path points to a directory. - * @return GIT_SUCCESS if it is a directory, < 0 otherwise. + * @return true or false */ -extern int git_path_isdir(const char *path); +extern bool git_path_isdir(const char *path); /** * Check if the given path points to a regular file. - * @return GIT_SUCCESS if it is a regular file, < 0 otherwise. + * @return true or false */ -extern int git_path_isfile(const char *path); +extern bool git_path_isfile(const char *path); + +/** + * Stat a file and/or link and set error if needed. + */ +extern int git_path_lstat(const char *path, struct stat *st); /** * Check if the parent directory contains the item. * * @param dir Directory to check. * @param item Item that might be in the directory. - * @return GIT_SUCCESS if item exists in directory, <0 otherwise. + * @return 0 if item exists in directory, <0 otherwise. */ -extern int git_path_contains(git_buf *dir, const char *item); +extern bool git_path_contains(git_buf *dir, const char *item); /** * Check if the given path contains the given subdirectory. * * @param parent Directory path that might contain subdir * @param subdir Subdirectory name to look for in parent - * @return GIT_SUCCESS if subdirectory exists, < 0 otherwise. + * @param append_if_exists If true, then subdir will be appended to the parent path if it does exist + * @return true if subdirectory exists, false otherwise. */ -extern int git_path_contains_dir(git_buf *parent, const char *subdir); +extern bool git_path_contains_dir(git_buf *parent, const char *subdir); /** * Check if the given path contains the given file. * * @param dir Directory path that might contain file * @param file File name to look for in parent - * @return GIT_SUCCESS if file exists, < 0 otherwise. + * @param append_if_exists If true, then file will be appended to the path if it does exist + * @return true if file exists, false otherwise. */ -extern int git_path_contains_file(git_buf *dir, const char *file); +extern bool git_path_contains_file(git_buf *dir, const char *file); /** * Clean up path, prepending base if it is not already rooted. @@ -197,14 +204,14 @@ extern int git_path_direach( * Sort function to order two paths. */ extern int git_path_cmp( - const char *name1, int len1, int isdir1, - const char *name2, int len2, int isdir2); + const char *name1, size_t len1, int isdir1, + const char *name2, size_t len2, int isdir2); /** * Invoke callback up path directory by directory until the ceiling is * reached (inclusive of a final call at the root_path). * - * Returning anything other than GIT_SUCCESS from the callback function + * Returning anything other than 0 from the callback function * will stop the iteration and propogate the error to the caller. * * @param pathbuf Buffer the function reads the directory from and @@ -31,13 +31,12 @@ static int flush_pkt(git_pkt **out) git_pkt *pkt; pkt = git__malloc(sizeof(git_pkt)); - if (pkt == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(pkt); pkt->type = GIT_PKT_FLUSH; *out = pkt; - return GIT_SUCCESS; + return 0; } /* the rest of the line will be useful for multi_ack */ @@ -48,13 +47,12 @@ static int ack_pkt(git_pkt **out, const char *line, size_t len) GIT_UNUSED(len); pkt = git__malloc(sizeof(git_pkt)); - if (pkt == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(pkt); pkt->type = GIT_PKT_ACK; *out = pkt; - return GIT_SUCCESS; + return 0; } static int nak_pkt(git_pkt **out) @@ -62,13 +60,12 @@ static int nak_pkt(git_pkt **out) git_pkt *pkt; pkt = git__malloc(sizeof(git_pkt)); - if (pkt == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(pkt); pkt->type = GIT_PKT_NAK; *out = pkt; - return GIT_SUCCESS; + return 0; } static int pack_pkt(git_pkt **out) @@ -76,13 +73,12 @@ static int pack_pkt(git_pkt **out) git_pkt *pkt; pkt = git__malloc(sizeof(git_pkt)); - if (pkt == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(pkt); pkt->type = GIT_PKT_PACK; *out = pkt; - return GIT_SUCCESS; + return 0; } static int comment_pkt(git_pkt **out, const char *line, size_t len) @@ -90,8 +86,7 @@ static int comment_pkt(git_pkt **out, const char *line, size_t len) git_pkt_comment *pkt; pkt = git__malloc(sizeof(git_pkt_comment) + len + 1); - if (pkt == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(pkt); pkt->type = GIT_PKT_COMMENT; memcpy(pkt->comment, line, len); @@ -99,7 +94,26 @@ static int comment_pkt(git_pkt **out, const char *line, size_t len) *out = (git_pkt *) pkt; - return GIT_SUCCESS; + return 0; +} + +static int err_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_err *pkt; + + /* Remove "ERR " from the line */ + line += 4; + len -= 4; + pkt = git__malloc(sizeof(git_pkt_err) + len + 1); + GITERR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_ERR; + memcpy(pkt->error, line, len); + pkt->error[len] = '\0'; + + *out = (git_pkt *) pkt; + + return 0; } /* @@ -107,25 +121,22 @@ static int comment_pkt(git_pkt **out, const char *line, size_t len) */ static int ref_pkt(git_pkt **out, const char *line, size_t len) { - git_pkt_ref *pkt; int error; + git_pkt_ref *pkt; pkt = git__malloc(sizeof(git_pkt_ref)); - if (pkt == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(pkt); memset(pkt, 0x0, sizeof(git_pkt_ref)); pkt->type = GIT_PKT_REF; - error = git_oid_fromstr(&pkt->head.oid, line); - if (error < GIT_SUCCESS) { - error = git__throw(error, "Failed to parse reference ID"); - goto out; - } + if ((error = git_oid_fromstr(&pkt->head.oid, line)) < 0) + goto error_out; /* Check for a bit of consistency */ if (line[GIT_OID_HEXSZ] != ' ') { - error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse ref. No SP"); - goto out; + giterr_set(GITERR_NET, "Error parsing pkt-line"); + error = -1; + goto error_out; } /* Jump from the name */ @@ -136,10 +147,8 @@ static int ref_pkt(git_pkt **out, const char *line, size_t len) --len; pkt->head.name = git__malloc(len + 1); - if (pkt->head.name == NULL) { - error = GIT_ENOMEM; - goto out; - } + GITERR_CHECK_ALLOC(pkt->head.name); + memcpy(pkt->head.name, line, len); pkt->head.name[len] = '\0'; @@ -147,36 +156,35 @@ static int ref_pkt(git_pkt **out, const char *line, size_t len) pkt->capabilities = strchr(pkt->head.name, '\0') + 1; } -out: - if (error < GIT_SUCCESS) - git__free(pkt); - else - *out = (git_pkt *)pkt; + *out = (git_pkt *)pkt; + return 0; +error_out: + git__free(pkt); return error; } -static ssize_t parse_len(const char *line) +static int32_t parse_len(const char *line) { char num[PKT_LEN_SIZE + 1]; int i, error; - int len; + int32_t len; const char *num_end; memcpy(num, line, PKT_LEN_SIZE); num[PKT_LEN_SIZE] = '\0'; for (i = 0; i < PKT_LEN_SIZE; ++i) { - if (!isxdigit(num[i])) - return GIT_ENOTNUM; + if (!isxdigit(num[i])) { + giterr_set(GITERR_NET, "Found invalid hex digit in length"); + return -1; + } } - error = git__strtol32(&len, num, &num_end, 16); - if (error < GIT_SUCCESS) { + if ((error = git__strtol32(&len, num, &num_end, 16)) < 0) return error; - } - return (unsigned int) len; + return len; } /* @@ -192,17 +200,18 @@ static ssize_t parse_len(const char *line) * in ASCII hexadecimal (including itself) */ -int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t bufflen) +int git_pkt_parse_line( + git_pkt **head, const char *line, const char **out, size_t bufflen) { - int error = GIT_SUCCESS; - size_t len; + int ret; + int32_t len; /* Not even enough for the length */ if (bufflen > 0 && bufflen < PKT_LEN_SIZE) return GIT_ESHORTBUFFER; - error = parse_len(line); - if (error < GIT_SUCCESS) { + len = parse_len(line); + if (len < 0) { /* * If we fail to parse the length, it might be because the * server is trying to send us the packfile already. @@ -212,16 +221,14 @@ int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_ return pack_pkt(head); } - return git__throw(error, "Failed to parse pkt length"); + return (int)len; } - len = error; - /* * If we were given a buffer length, then make sure there is * enough in the buffer to satisfy this line */ - if (bufflen > 0 && bufflen < len) + if (bufflen > 0 && bufflen < (size_t)len) return GIT_ESHORTBUFFER; line += PKT_LEN_SIZE; @@ -231,7 +238,7 @@ int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_ */ if (len == PKT_LEN_SIZE) { *out = line; - return GIT_SUCCESS; + return 0; } if (len == 0) { /* Flush pkt */ @@ -243,22 +250,24 @@ int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_ /* Assming the minimal size is actually 4 */ if (!git__prefixcmp(line, "ACK")) - error = ack_pkt(head, line, len); + ret = ack_pkt(head, line, len); else if (!git__prefixcmp(line, "NAK")) - error = nak_pkt(head); + ret = nak_pkt(head); + else if (!git__prefixcmp(line, "ERR ")) + ret = err_pkt(head, line, len); else if (*line == '#') - error = comment_pkt(head, line, len); + ret = comment_pkt(head, line, len); else - error = ref_pkt(head, line, len); + ret = ref_pkt(head, line, len); *out = line + len; - return error; + return ret; } void git_pkt_free(git_pkt *pkt) { - if(pkt->type == GIT_PKT_REF) { + if (pkt->type == GIT_PKT_REF) { git_pkt_ref *p = (git_pkt_ref *) pkt; git__free(p->head.name); } @@ -271,7 +280,7 @@ int git_pkt_buffer_flush(git_buf *buf) return git_buf_put(buf, pkt_flush_str, strlen(pkt_flush_str)); } -int git_pkt_send_flush(int s) +int git_pkt_send_flush(GIT_SOCKET s) { return gitno_send(s, pkt_flush_str, strlen(pkt_flush_str), 0); @@ -281,33 +290,20 @@ static int buffer_want_with_caps(git_remote_head *head, git_transport_caps *caps { char capstr[20]; char oid[GIT_OID_HEXSZ +1] = {0}; - int len; + unsigned int len; if (caps->ofs_delta) - strcpy(capstr, GIT_CAP_OFS_DELTA); + strncpy(capstr, GIT_CAP_OFS_DELTA, sizeof(capstr)); - len = strlen("XXXXwant ") + GIT_OID_HEXSZ + 1 /* NUL */ + strlen(capstr) + 1 /* LF */; - git_buf_grow(buf, buf->size + len); + len = (unsigned int) + (strlen("XXXXwant ") + GIT_OID_HEXSZ + 1 /* NUL */ + + strlen(capstr) + 1 /* LF */); + git_buf_grow(buf, git_buf_len(buf) + len); git_oid_fmt(oid, &head->oid); return git_buf_printf(buf, "%04xwant %s%c%s\n", len, oid, 0, capstr); } -static int send_want_with_caps(git_remote_head *head, git_transport_caps *caps, GIT_SOCKET fd) -{ - git_buf buf = GIT_BUF_INIT; - int error; - - error = buffer_want_with_caps(head, caps, &buf); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to buffer want with caps"); - - error = gitno_send(fd, buf.ptr, buf.size, 0); - git_buf_free(&buf); - - return error; -} - /* * All "want" packets have the same length and format, so what we do * is overwrite the OID each time. @@ -316,7 +312,6 @@ static int send_want_with_caps(git_remote_head *head, git_transport_caps *caps, int git_pkt_buffer_wants(const git_vector *refs, git_transport_caps *caps, git_buf *buf) { unsigned int i = 0; - int error; git_remote_head *head; if (caps->common) { @@ -326,9 +321,8 @@ int git_pkt_buffer_wants(const git_vector *refs, git_transport_caps *caps, git_b break; } - error = buffer_want_with_caps(refs->contents[i], caps, buf); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to buffer want with caps"); + if (buffer_want_with_caps(refs->contents[i], caps, buf) < 0) + return -1; i++; } @@ -344,54 +338,13 @@ int git_pkt_buffer_wants(const git_vector *refs, git_transport_caps *caps, git_b git_buf_put(buf, pkt_want_prefix, strlen(pkt_want_prefix)); git_buf_put(buf, oid, GIT_OID_HEXSZ); git_buf_putc(buf, '\n'); + if (git_buf_oom(buf)) + return -1; } return git_pkt_buffer_flush(buf); } -int git_pkt_send_wants(const git_vector *refs, git_transport_caps *caps, int fd) -{ - unsigned int i = 0; - int error = GIT_SUCCESS; - char buf[sizeof(pkt_want_prefix) + GIT_OID_HEXSZ + 1]; - git_remote_head *head; - - memcpy(buf, pkt_want_prefix, strlen(pkt_want_prefix)); - buf[sizeof(buf) - 2] = '\n'; - buf[sizeof(buf) - 1] = '\0'; - - /* If there are common caps, find the first one */ - if (caps->common) { - for (; i < refs->length; ++i) { - head = refs->contents[i]; - if (head->local) - continue; - else - break; - } - - error = send_want_with_caps(refs->contents[i], caps, fd); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to send want pkt with caps"); - /* Increase it here so it's correct whether we run this or not */ - i++; - } - - /* Continue from where we left off */ - for (; i < refs->length; ++i) { - head = refs->contents[i]; - if (head->local) - continue; - - git_oid_fmt(buf + strlen(pkt_want_prefix), &head->oid); - error = gitno_send(fd, buf, strlen(buf), 0); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to send want pkt"); - } - - return git_pkt_send_flush(fd); -} - int git_pkt_buffer_have(git_oid *oid, git_buf *buf) { char oidhex[GIT_OID_HEXSZ + 1]; @@ -401,21 +354,7 @@ int git_pkt_buffer_have(git_oid *oid, git_buf *buf) return git_buf_printf(buf, "%s%s\n", pkt_have_prefix, oidhex); } -int git_pkt_send_have(git_oid *oid, int fd) -{ - char buf[] = "0032have 0000000000000000000000000000000000000000\n"; - - git_oid_fmt(buf + strlen(pkt_have_prefix), oid); - return gitno_send(fd, buf, strlen(buf), 0); -} - - int git_pkt_buffer_done(git_buf *buf) { return git_buf_puts(buf, pkt_done_str); } - -int git_pkt_send_done(int fd) -{ - return gitno_send(fd, pkt_done_str, strlen(pkt_done_str), 0); -} @@ -11,6 +11,7 @@ #include "common.h" #include "transport.h" #include "buffer.h" +#include "posix.h" #include "git2/net.h" enum git_pkt_type { @@ -22,6 +23,7 @@ enum git_pkt_type { GIT_PKT_NAK, GIT_PKT_PACK, GIT_PKT_COMMENT, + GIT_PKT_ERR, }; /* Used for multi-ack */ @@ -63,15 +65,17 @@ typedef struct { char comment[GIT_FLEX_ARRAY]; } git_pkt_comment; +typedef struct { + enum git_pkt_type type; + char error[GIT_FLEX_ARRAY]; +} git_pkt_err; + int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len); int git_pkt_buffer_flush(git_buf *buf); -int git_pkt_send_flush(int s); +int git_pkt_send_flush(GIT_SOCKET s); int git_pkt_buffer_done(git_buf *buf); -int git_pkt_send_done(int s); int git_pkt_buffer_wants(const git_vector *refs, git_transport_caps *caps, git_buf *buf); -int git_pkt_send_wants(const git_vector *refs, git_transport_caps *caps, int fd); int git_pkt_buffer_have(git_oid *oid, git_buf *buf); -int git_pkt_send_have(git_oid *oid, int fd); void git_pkt_free(git_pkt *pkt); #endif diff --git a/src/pool.c b/src/pool.c new file mode 100644 index 000000000..641292d06 --- /dev/null +++ b/src/pool.c @@ -0,0 +1,294 @@ +#include "pool.h" +#ifndef GIT_WIN32 +#include <unistd.h> +#endif + +struct git_pool_page { + git_pool_page *next; + uint32_t size; + uint32_t avail; + char data[GIT_FLEX_ARRAY]; +}; + +#define GIT_POOL_MIN_USABLE 4 +#define GIT_POOL_MIN_PAGESZ 2 * sizeof(void*) + +static void *pool_alloc_page(git_pool *pool, uint32_t size); +static void pool_insert_page(git_pool *pool, git_pool_page *page); + +int git_pool_init( + git_pool *pool, uint32_t item_size, uint32_t items_per_page) +{ + assert(pool); + + if (!item_size) + item_size = 1; + /* round up item_size for decent object alignment */ + if (item_size > 4) + item_size = (item_size + 7) & ~7; + else if (item_size == 3) + item_size = 4; + + if (!items_per_page) + items_per_page = git_pool__suggest_items_per_page(item_size); + if (item_size * items_per_page < GIT_POOL_MIN_PAGESZ) + items_per_page = (GIT_POOL_MIN_PAGESZ + item_size - 1) / item_size; + + memset(pool, 0, sizeof(git_pool)); + pool->item_size = item_size; + pool->page_size = item_size * items_per_page; + + return 0; +} + +void git_pool_clear(git_pool *pool) +{ + git_pool_page *scan, *next; + + for (scan = pool->open; scan != NULL; scan = next) { + next = scan->next; + git__free(scan); + } + pool->open = NULL; + + for (scan = pool->full; scan != NULL; scan = next) { + next = scan->next; + git__free(scan); + } + pool->full = NULL; + + pool->free_list = NULL; + + pool->items = 0; + + pool->has_string_alloc = 0; + pool->has_multi_item_alloc = 0; + pool->has_large_page_alloc = 0; +} + +void git_pool_swap(git_pool *a, git_pool *b) +{ + git_pool temp; + + if (a == b) + return; + + memcpy(&temp, a, sizeof(temp)); + memcpy(a, b, sizeof(temp)); + memcpy(b, &temp, sizeof(temp)); +} + +static void pool_insert_page(git_pool *pool, git_pool_page *page) +{ + git_pool_page *scan; + + /* If there are no open pages or this page has the most open space, + * insert it at the beginning of the list. This is the common case. + */ + if (pool->open == NULL || pool->open->avail < page->avail) { + page->next = pool->open; + pool->open = page; + return; + } + + /* Otherwise insert into sorted position. */ + for (scan = pool->open; + scan->next && scan->next->avail > page->avail; + scan = scan->next); + page->next = scan->next; + scan->next = page; +} + +static void *pool_alloc_page(git_pool *pool, uint32_t size) +{ + git_pool_page *page; + uint32_t alloc_size; + + if (size <= pool->page_size) + alloc_size = pool->page_size; + else { + alloc_size = size; + pool->has_large_page_alloc = 1; + } + + page = git__calloc(1, alloc_size + sizeof(git_pool_page)); + if (!page) + return NULL; + + page->size = alloc_size; + page->avail = alloc_size - size; + + if (page->avail > 0) + pool_insert_page(pool, page); + else { + page->next = pool->full; + pool->full = page; + } + + pool->items++; + + return page->data; +} + +GIT_INLINE(void) pool_remove_page( + git_pool *pool, git_pool_page *page, git_pool_page *prev) +{ + if (prev == NULL) + pool->open = page->next; + else + prev->next = page->next; +} + +void *git_pool_malloc(git_pool *pool, uint32_t items) +{ + git_pool_page *scan = pool->open, *prev; + uint32_t size = items * pool->item_size; + void *ptr = NULL; + + pool->has_string_alloc = 0; + if (items > 1) + pool->has_multi_item_alloc = 1; + else if (pool->free_list != NULL) { + ptr = pool->free_list; + pool->free_list = *((void **)pool->free_list); + return ptr; + } + + /* just add a block if there is no open one to accomodate this */ + if (size >= pool->page_size || !scan || scan->avail < size) + return pool_alloc_page(pool, size); + + pool->items++; + + /* find smallest block in free list with space */ + for (scan = pool->open, prev = NULL; + scan->next && scan->next->avail >= size; + prev = scan, scan = scan->next); + + /* allocate space from the block */ + ptr = &scan->data[scan->size - scan->avail]; + scan->avail -= size; + + /* move to full list if there is almost no space left */ + if (scan->avail < pool->item_size || scan->avail < GIT_POOL_MIN_USABLE) { + pool_remove_page(pool, scan, prev); + scan->next = pool->full; + pool->full = scan; + } + /* reorder list if block is now smaller than the one after it */ + else if (scan->next != NULL && scan->next->avail > scan->avail) { + pool_remove_page(pool, scan, prev); + pool_insert_page(pool, scan); + } + + return ptr; +} + +char *git_pool_strndup(git_pool *pool, const char *str, size_t n) +{ + void *ptr = NULL; + + assert(pool && str && pool->item_size == sizeof(char)); + + if ((ptr = git_pool_malloc(pool, (uint32_t)(n + 1))) != NULL) { + memcpy(ptr, str, n); + *(((char *)ptr) + n) = '\0'; + } + pool->has_string_alloc = 1; + + return ptr; +} + +char *git_pool_strdup(git_pool *pool, const char *str) +{ + assert(pool && str && pool->item_size == sizeof(char)); + + return git_pool_strndup(pool, str, strlen(str)); +} + +char *git_pool_strcat(git_pool *pool, const char *a, const char *b) +{ + void *ptr; + size_t len_a, len_b; + + assert(pool && a && b && pool->item_size == sizeof(char)); + + len_a = a ? strlen(a) : 0; + len_b = b ? strlen(b) : 0; + + if ((ptr = git_pool_malloc(pool, (uint32_t)(len_a + len_b + 1))) != NULL) { + if (len_a) + memcpy(ptr, a, len_a); + if (len_b) + memcpy(((char *)ptr) + len_a, b, len_b); + *(((char *)ptr) + len_a + len_b) = '\0'; + } + pool->has_string_alloc = 1; + + return ptr; +} + +void git_pool_free(git_pool *pool, void *ptr) +{ + assert(pool && ptr && pool->item_size >= sizeof(void*)); + + *((void **)ptr) = pool->free_list; + pool->free_list = ptr; +} + +uint32_t git_pool__open_pages(git_pool *pool) +{ + uint32_t ct = 0; + git_pool_page *scan; + for (scan = pool->open; scan != NULL; scan = scan->next) ct++; + return ct; +} + +uint32_t git_pool__full_pages(git_pool *pool) +{ + uint32_t ct = 0; + git_pool_page *scan; + for (scan = pool->full; scan != NULL; scan = scan->next) ct++; + return ct; +} + +bool git_pool__ptr_in_pool(git_pool *pool, void *ptr) +{ + git_pool_page *scan; + for (scan = pool->open; scan != NULL; scan = scan->next) + if ((void *)scan->data <= ptr && + (void *)(((char *)scan->data) + scan->size) > ptr) + return true; + for (scan = pool->full; scan != NULL; scan = scan->next) + if ((void *)scan->data <= ptr && + (void *)(((char *)scan->data) + scan->size) > ptr) + return true; + return false; +} + +uint32_t git_pool__system_page_size(void) +{ + static uint32_t size = 0; + + if (!size) { +#ifdef GIT_WIN32 + SYSTEM_INFO info; + GetSystemInfo(&info); + size = (uint32_t)info.dwPageSize; +#else + size = (uint32_t)sysconf(_SC_PAGE_SIZE); +#endif + + size -= 2 * sizeof(void *); /* allow space for malloc overhead */ + } + + return size; +} + +uint32_t git_pool__suggest_items_per_page(uint32_t item_size) +{ + uint32_t page_bytes = + git_pool__system_page_size() - sizeof(git_pool_page); + return page_bytes / item_size; +} + diff --git a/src/pool.h b/src/pool.h new file mode 100644 index 000000000..54a2861ed --- /dev/null +++ b/src/pool.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_pool_h__ +#define INCLUDE_pool_h__ + +#include "common.h" + +typedef struct git_pool_page git_pool_page; + +/** + * Chunked allocator. + * + * A `git_pool` can be used when you want to cheaply allocate + * multiple items of the same type and are willing to free them + * all together with a single call. The two most common cases + * are a set of fixed size items (such as lots of OIDs) or a + * bunch of strings. + * + * Internally, a `git_pool` allocates pages of memory and then + * deals out blocks from the trailing unused portion of each page. + * The pages guarantee that the number of actual allocations done + * will be much smaller than the number of items needed. + * + * For examples of how to set up a `git_pool` see `git_pool_init`. + */ +typedef struct { + git_pool_page *open; /* pages with space left */ + git_pool_page *full; /* pages with no space left */ + void *free_list; /* optional: list of freed blocks */ + uint32_t item_size; /* size of single alloc unit in bytes */ + uint32_t page_size; /* size of page in bytes */ + uint32_t items; + unsigned has_string_alloc : 1; /* was the strdup function used */ + unsigned has_multi_item_alloc : 1; /* was items ever > 1 in malloc */ + unsigned has_large_page_alloc : 1; /* are any pages > page_size */ +} git_pool; + +#define GIT_POOL_INIT_STRINGPOOL { 0, 0, 0, 1, 4000, 0, 0, 0, 0 } + +/** + * Initialize a pool. + * + * To allocation strings, use like this: + * + * git_pool_init(&string_pool, 1, 0); + * my_string = git_pool_strdup(&string_pool, your_string); + * + * To allocate items of fixed size, use like this: + * + * git_pool_init(&pool, sizeof(item), 0); + * my_item = git_pool_malloc(&pool, 1); + * + * Of course, you can use this in other ways, but those are the + * two most common patterns. + */ +extern int git_pool_init( + git_pool *pool, uint32_t item_size, uint32_t items_per_page); + +/** + * Free all items in pool + */ +extern void git_pool_clear(git_pool *pool); + +/** + * Swap two pools with one another + */ +extern void git_pool_swap(git_pool *a, git_pool *b); + +/** + * Allocate space for one or more items from a pool. + */ +extern void *git_pool_malloc(git_pool *pool, uint32_t items); + +/** + * Allocate space and duplicate string data into it. + * + * This is allowed only for pools with item_size == sizeof(char) + */ +extern char *git_pool_strndup(git_pool *pool, const char *str, size_t n); + +/** + * Allocate space and duplicate a string into it. + * + * This is allowed only for pools with item_size == sizeof(char) + */ +extern char *git_pool_strdup(git_pool *pool, const char *str); + +/** + * Allocate space for the concatenation of two strings. + * + * This is allowed only for pools with item_size == sizeof(char) + */ +extern char *git_pool_strcat(git_pool *pool, const char *a, const char *b); + +/** + * Push a block back onto the free list for the pool. + * + * This is allowed only if the item_size is >= sizeof(void*). + * + * In some cases, it is helpful to "release" an allocated block + * for reuse. Pools don't support a general purpose free, but + * they will keep a simple free blocks linked list provided the + * native block size is large enough to hold a void pointer + */ +extern void git_pool_free(git_pool *pool, void *ptr); + +/* + * Misc utilities + */ + +extern uint32_t git_pool__open_pages(git_pool *pool); + +extern uint32_t git_pool__full_pages(git_pool *pool); + +extern bool git_pool__ptr_in_pool(git_pool *pool, void *ptr); + +extern uint32_t git_pool__system_page_size(void); + +extern uint32_t git_pool__suggest_items_per_page(uint32_t item_size); + +#endif diff --git a/src/posix.c b/src/posix.c index d2364d9b4..a3f81d767 100644 --- a/src/posix.c +++ b/src/posix.c @@ -31,27 +31,25 @@ int p_getcwd(char *buffer_out, size_t size) cwd_buffer = getcwd(buffer_out, size); if (cwd_buffer == NULL) - return git__throw(GIT_EOSERR, "Failed to retrieve current working directory"); + return -1; git_path_mkposix(buffer_out); + git_path_string_to_dir(buffer_out, size); /* append trailing slash */ - git_path_string_to_dir(buffer_out, size); //Ensure the path ends with a trailing slash - - return GIT_SUCCESS; + return 0; } int p_rename(const char *from, const char *to) { if (!link(from, to)) { p_unlink(from); - return GIT_SUCCESS; + return 0; } if (!rename(from, to)) - return GIT_SUCCESS; - - return GIT_ERROR; + return 0; + return -1; } #endif @@ -60,11 +58,17 @@ int p_read(git_file fd, void *buf, size_t cnt) { char *b = buf; while (cnt) { - ssize_t r = read(fd, b, cnt); + ssize_t r; +#ifdef GIT_WIN32 + assert((size_t)((unsigned int)cnt) == cnt); + r = read(fd, b, (unsigned int)cnt); +#else + r = read(fd, b, cnt); +#endif if (r < 0) { if (errno == EINTR || errno == EAGAIN) continue; - return GIT_EOSERR; + return -1; } if (!r) break; @@ -78,18 +82,24 @@ int p_write(git_file fd, const void *buf, size_t cnt) { const char *b = buf; while (cnt) { - ssize_t r = write(fd, b, cnt); + ssize_t r; +#ifdef GIT_WIN32 + assert((size_t)((unsigned int)cnt) == cnt); + r = write(fd, b, (unsigned int)cnt); +#else + r = write(fd, b, cnt); +#endif if (r < 0) { if (errno == EINTR || errno == EAGAIN) continue; - return GIT_EOSERR; + return -1; } if (!r) { errno = EPIPE; - return GIT_EOSERR; + return -1; } cnt -= r; b += r; } - return GIT_SUCCESS; + return 0; } diff --git a/src/posix.h b/src/posix.h index fb17cba6c..752d5156f 100644 --- a/src/posix.h +++ b/src/posix.h @@ -54,6 +54,14 @@ extern int p_rename(const char *from, const char *to); #define p_rmdir(p) rmdir(p) #define p_chmod(p,m) chmod(p, m) #define p_access(p,m) access(p,m) +#define p_recv(s,b,l,f) recv(s,b,l,f) +#define p_send(s,b,l,f) send(s,b,l,f) +typedef int GIT_SOCKET; +#define INVALID_SOCKET -1 + +#else + +typedef SOCKET GIT_SOCKET; #endif diff --git a/src/pqueue.c b/src/pqueue.c index 3fbf93315..cb59c13ec 100644 --- a/src/pqueue.c +++ b/src/pqueue.c @@ -17,14 +17,14 @@ int git_pqueue_init(git_pqueue *q, size_t n, git_pqueue_cmp cmppri) assert(q); /* Need to allocate n+1 elements since element 0 isn't used. */ - if ((q->d = git__malloc((n + 1) * sizeof(void *))) == NULL) - return GIT_ENOMEM; + q->d = git__malloc((n + 1) * sizeof(void *)); + GITERR_CHECK_ALLOC(q->d); q->size = 1; q->avail = q->step = (n + 1); /* see comment above about n+1 */ q->cmppri = cmppri; - return GIT_SUCCESS; + return 0; } @@ -102,8 +102,8 @@ int git_pqueue_insert(git_pqueue *q, void *d) /* allocate more memory if necessary */ if (q->size >= q->avail) { newsize = q->size + q->step; - if ((tmp = git__realloc(q->d, sizeof(void *) * newsize)) == NULL) - return GIT_ENOMEM; + tmp = git__realloc(q->d, sizeof(void *) * newsize); + GITERR_CHECK_ALLOC(tmp); q->d = tmp; q->avail = newsize; @@ -114,7 +114,7 @@ int git_pqueue_insert(git_pqueue *q, void *d) q->d[i] = d; bubble_up(q, i); - return GIT_SUCCESS; + return 0; } diff --git a/src/protocol.c b/src/protocol.c index dd93623b3..a75354121 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -17,10 +17,12 @@ int git_protocol_store_refs(git_protocol *p, const char *data, size_t len) const char *line_end, *ptr; if (len == 0) { /* EOF */ - if (buf->size != 0) - return p->error = git__throw(GIT_ERROR, "EOF and unprocessed data"); - else + if (git_buf_len(buf) != 0) { + giterr_set(GITERR_NET, "Unexpected EOF"); + return p->error = -1; + } else { return 0; + } } git_buf_put(buf, data, len); @@ -28,23 +30,29 @@ int git_protocol_store_refs(git_protocol *p, const char *data, size_t len) while (1) { git_pkt *pkt; - if (buf->size == 0) + if (git_buf_len(buf) == 0) return 0; - error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->size); + error = git_pkt_parse_line(&pkt, ptr, &line_end, git_buf_len(buf)); if (error == GIT_ESHORTBUFFER) return 0; /* Ask for more */ - if (error < GIT_SUCCESS) - return p->error = git__rethrow(error, "Failed to parse pkt-line"); + if (error < 0) + return p->error = -1; git_buf_consume(buf, line_end); - error = git_vector_insert(refs, pkt); - if (error < GIT_SUCCESS) - return p->error = git__rethrow(error, "Failed to add pkt to list"); + + if (pkt->type == GIT_PKT_ERR) { + giterr_set(GITERR_NET, "Remote error: %s", ((git_pkt_err *)pkt)->error); + git__free(pkt); + return -1; + } + + if (git_vector_insert(refs, pkt) < 0) + return p->error = -1; if (pkt->type == GIT_PKT_FLUSH) p->flush = 1; } - return error; + return 0; } diff --git a/src/reflog.c b/src/reflog.c index e8b0b9811..3ea073e65 100644 --- a/src/reflog.c +++ b/src/reflog.c @@ -16,23 +16,21 @@ static int reflog_init(git_reflog **reflog, git_reference *ref) *reflog = NULL; - log = git__malloc(sizeof(git_reflog)); - if (log == NULL) - return GIT_ENOMEM; - - memset(log, 0x0, sizeof(git_reflog)); + log = git__calloc(1, sizeof(git_reflog)); + GITERR_CHECK_ALLOC(log); log->ref_name = git__strdup(ref->name); + GITERR_CHECK_ALLOC(log->ref_name); if (git_vector_init(&log->entries, 0, NULL) < 0) { git__free(log->ref_name); git__free(log); - return GIT_ENOMEM; + return -1; } *reflog = log; - return GIT_SUCCESS; + return 0; } static int reflog_write(const char *log_path, const char *oid_old, @@ -42,9 +40,22 @@ static int reflog_write(const char *log_path, const char *oid_old, int error; git_buf log = GIT_BUF_INIT; git_filebuf fbuf = GIT_FILEBUF_INIT; + bool trailing_newline = false; assert(log_path && oid_old && oid_new && committer); + if (msg) { + const char *newline = strchr(msg, '\n'); + if (newline) { + if (*(newline + 1) == '\0') + trailing_newline = true; + else { + giterr_set(GITERR_INVALID, "Reflog message cannot contain newline"); + return -1; + } + } + } + git_buf_puts(&log, oid_old); git_buf_putc(&log, ' '); @@ -54,68 +65,58 @@ static int reflog_write(const char *log_path, const char *oid_old, git_buf_truncate(&log, log.size - 1); /* drop LF */ if (msg) { - if (strchr(msg, '\n')) { - git_buf_free(&log); - return git__throw(GIT_ERROR, "Reflog message cannot contain newline"); - } - git_buf_putc(&log, '\t'); git_buf_puts(&log, msg); } - git_buf_putc(&log, '\n'); + if (!trailing_newline) + git_buf_putc(&log, '\n'); - if ((error = git_buf_lasterror(&log)) < GIT_SUCCESS) { + if (git_buf_oom(&log)) { git_buf_free(&log); - return git__rethrow(error, "Failed to write reflog. Memory allocation failure"); + return -1; } - if ((error = git_filebuf_open(&fbuf, log_path, GIT_FILEBUF_APPEND)) < GIT_SUCCESS) { - git_buf_free(&log); - return git__rethrow(error, "Failed to write reflog. Cannot open reflog `%s`", log_path); + error = git_filebuf_open(&fbuf, log_path, GIT_FILEBUF_APPEND); + if (!error) { + if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0) + git_filebuf_cleanup(&fbuf); + else + error = git_filebuf_commit(&fbuf, GIT_REFLOG_FILE_MODE); } - git_filebuf_write(&fbuf, log.ptr, log.size); - error = git_filebuf_commit(&fbuf, GIT_REFLOG_FILE_MODE); - git_buf_free(&log); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write reflog"); + return error; } static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) { - int error = GIT_SUCCESS; const char *ptr; git_reflog_entry *entry; -#define seek_forward(_increase) { \ +#define seek_forward(_increase) do { \ if (_increase >= buf_size) { \ - if (entry->committer) \ - git__free(entry->committer); \ - git__free(entry); \ - return git__throw(GIT_ERROR, "Failed to seek forward. Buffer size exceeded"); \ + giterr_set(GITERR_INVALID, "Ran out of data while parsing reflog"); \ + goto fail; \ } \ buf += _increase; \ buf_size -= _increase; \ -} + } while (0) while (buf_size > GIT_REFLOG_SIZE_MIN) { entry = git__malloc(sizeof(git_reflog_entry)); - if (entry == NULL) - return GIT_ENOMEM; - entry->committer = NULL; + GITERR_CHECK_ALLOC(entry); - if (git_oid_fromstrn(&entry->oid_old, buf, GIT_OID_HEXSZ) < GIT_SUCCESS) { - git__free(entry); - return GIT_ERROR; - } + entry->committer = git__malloc(sizeof(git_signature)); + GITERR_CHECK_ALLOC(entry->committer); + + if (git_oid_fromstrn(&entry->oid_old, buf, GIT_OID_HEXSZ) < 0) + goto fail; seek_forward(GIT_OID_HEXSZ + 1); - if (git_oid_fromstrn(&entry->oid_cur, buf, GIT_OID_HEXSZ) < GIT_SUCCESS) { - git__free(entry); - return GIT_ERROR; - } + if (git_oid_fromstrn(&entry->oid_cur, buf, GIT_OID_HEXSZ) < 0) + goto fail; seek_forward(GIT_OID_HEXSZ + 1); ptr = buf; @@ -124,17 +125,8 @@ static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) while (*buf && *buf != '\t' && *buf != '\n') seek_forward(1); - entry->committer = git__malloc(sizeof(git_signature)); - if (entry->committer == NULL) { - git__free(entry); - return GIT_ENOMEM; - } - - if ((error = git_signature__parse(entry->committer, &ptr, buf + 1, NULL, *buf)) < GIT_SUCCESS) { - git__free(entry->committer); - git__free(entry); - return git__rethrow(error, "Failed to parse reflog. Could not parse signature"); - } + if (git_signature__parse(entry->committer, &ptr, buf + 1, NULL, *buf) < 0) + goto fail; if (*buf == '\t') { /* We got a message. Read everything till we reach LF. */ @@ -145,19 +137,27 @@ static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) seek_forward(1); entry->msg = git__strndup(ptr, buf - ptr); + GITERR_CHECK_ALLOC(entry->msg); } else entry->msg = NULL; while (*buf && *buf == '\n' && buf_size > 1) seek_forward(1); - if ((error = git_vector_insert(&log->entries, entry)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to parse reflog. Could not add new entry"); + if (git_vector_insert(&log->entries, entry) < 0) + goto fail; } + return 0; + #undef seek_forward - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse reflog"); +fail: + if (entry) { + git__free(entry->committer); + git__free(entry); + } + return -1; } void git_reflog_free(git_reflog *reflog) @@ -188,27 +188,23 @@ int git_reflog_read(git_reflog **reflog, git_reference *ref) *reflog = NULL; - if ((error = reflog_init(&log, ref)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to read reflog. Cannot init reflog"); + if (reflog_init(&log, ref) < 0) + return -1; error = git_buf_join_n(&log_path, '/', 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name); - if (error < GIT_SUCCESS) - goto cleanup; - if ((error = git_futils_readbuffer(&log_file, log_path.ptr)) < GIT_SUCCESS) { - git__rethrow(error, "Failed to read reflog. Cannot read file `%s`", log_path.ptr); - goto cleanup; - } + if (!error) + error = git_futils_readbuffer(&log_file, log_path.ptr); - if ((error = reflog_parse(log, log_file.ptr, log_file.size)) < GIT_SUCCESS) - git__rethrow(error, "Failed to read reflog"); - else - *reflog = log; + if (!error) + error = reflog_parse(log, log_file.ptr, log_file.size); -cleanup: - if (error != GIT_SUCCESS && log != NULL) + if (!error) + *reflog = log; + else git_reflog_free(log); + git_buf_free(&log_file); git_buf_free(&log_path); @@ -225,16 +221,15 @@ int git_reflog_write(git_reference *ref, const git_oid *oid_old, git_reference *r; const git_oid *oid; - if ((error = git_reference_resolve(&r, ref)) < GIT_SUCCESS) - return git__rethrow(error, - "Failed to write reflog. Cannot resolve reference `%s`", ref->name); + if ((error = git_reference_resolve(&r, ref)) < 0) + return error; oid = git_reference_oid(r); if (oid == NULL) { - error = git__throw(GIT_ERROR, + giterr_set(GITERR_REFERENCE, "Failed to write reflog. Cannot resolve reference `%s`", r->name); git_reference_free(r); - return error; + return -1; } git_oid_tostr(new, GIT_OID_HEXSZ+1, oid); @@ -243,23 +238,21 @@ int git_reflog_write(git_reference *ref, const git_oid *oid_old, error = git_buf_join_n(&log_path, '/', 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name); - if (error < GIT_SUCCESS) + if (error < 0) goto cleanup; - if (git_path_exists(log_path.ptr)) { + if (git_path_exists(log_path.ptr) == false) { error = git_futils_mkpath2file(log_path.ptr, GIT_REFLOG_DIR_MODE); - if (error < GIT_SUCCESS) - git__rethrow(error, - "Failed to write reflog. Cannot create reflog directory"); - } else if (git_path_isfile(log_path.ptr)) { - error = git__throw(GIT_ERROR, + } else if (git_path_isfile(log_path.ptr) == false) { + giterr_set(GITERR_REFERENCE, "Failed to write reflog. `%s` is directory", log_path.ptr); + error = -1; } else if (oid_old == NULL) { - error = git__throw(GIT_ERROR, + giterr_set(GITERR_REFERENCE, "Failed to write reflog. Old OID cannot be NULL for existing reference"); + error = -1; } - - if (error < GIT_SUCCESS) + if (error < 0) goto cleanup; if (oid_old) @@ -280,13 +273,13 @@ int git_reflog_rename(git_reference *ref, const char *new_name) git_buf old_path = GIT_BUF_INIT; git_buf new_path = GIT_BUF_INIT; - if (git_buf_join_n(&old_path, '/', 3, ref->owner->path_repository, - GIT_REFLOG_DIR, ref->name) && - git_buf_join_n(&new_path, '/', 3, ref->owner->path_repository, - GIT_REFLOG_DIR, new_name)) + if (!git_buf_join_n(&old_path, '/', 3, ref->owner->path_repository, + GIT_REFLOG_DIR, ref->name) && + !git_buf_join_n(&new_path, '/', 3, ref->owner->path_repository, + GIT_REFLOG_DIR, new_name)) error = p_rename(git_buf_cstr(&old_path), git_buf_cstr(&new_path)); else - error = GIT_ENOMEM; + error = -1; git_buf_free(&old_path); git_buf_free(&new_path); @@ -296,13 +289,13 @@ int git_reflog_rename(git_reference *ref, const char *new_name) int git_reflog_delete(git_reference *ref) { - int error = GIT_SUCCESS; + int error; git_buf path = GIT_BUF_INIT; - error = git_buf_join_n(&path, '/', 3, - ref->owner->path_repository, GIT_REFLOG_DIR, ref->name); + error = git_buf_join_n( + &path, '/', 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name); - if (error == GIT_SUCCESS && git_path_exists(path.ptr) == 0) + if (!error && git_path_exists(path.ptr)) error = p_unlink(path.ptr); git_buf_free(&path); diff --git a/src/refs.c b/src/refs.c index f3388bf53..28e8f786b 100644 --- a/src/refs.c +++ b/src/refs.c @@ -15,7 +15,10 @@ #include <git2/tag.h> #include <git2/object.h> -#define MAX_NESTING_LEVEL 5 +GIT__USE_STRMAP; + +#define DEFAULT_NESTING_LEVEL 5 +#define MAX_NESTING_LEVEL 10 enum { GIT_PACKREF_HAS_PEEL = 1, @@ -29,8 +32,6 @@ struct packref { char name[GIT_FLEX_ARRAY]; }; -static const int default_table_size = 32; - static int reference_read( git_buf *file_content, time_t *mtime, @@ -61,7 +62,7 @@ static int packed_lookup(git_reference *ref); static int packed_write(git_repository *repo); /* internal helpers */ -static int reference_available(git_repository *repo, +static int reference_path_available(git_repository *repo, const char *ref, const char *old_ref); static int reference_delete(git_reference *ref); static int reference_lookup(git_reference *ref); @@ -97,98 +98,100 @@ static int reference_alloc( assert(ref_out && repo && name); reference = git__malloc(sizeof(git_reference)); - if (reference == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(reference); memset(reference, 0x0, sizeof(git_reference)); reference->owner = repo; reference->name = git__strdup(name); - if (reference->name == NULL) { - git__free(reference); - return GIT_ENOMEM; - } + GITERR_CHECK_ALLOC(reference->name); *ref_out = reference; - return GIT_SUCCESS; + return 0; } -static int reference_read(git_buf *file_content, time_t *mtime, const char *repo_path, const char *ref_name, int *updated) +static int reference_read( + git_buf *file_content, + time_t *mtime, + const char *repo_path, + const char *ref_name, + int *updated) { git_buf path = GIT_BUF_INIT; - int error = GIT_SUCCESS; + int result; assert(file_content && repo_path && ref_name); /* Determine the full path of the file */ - if ((error = git_buf_joinpath(&path, repo_path, ref_name)) == GIT_SUCCESS) - error = git_futils_readbuffer_updated(file_content, path.ptr, mtime, updated); + if (git_buf_joinpath(&path, repo_path, ref_name) < 0) + return -1; + result = git_futils_readbuffer_updated(file_content, path.ptr, mtime, updated); git_buf_free(&path); - - return error; + return result; } static int loose_parse_symbolic(git_reference *ref, git_buf *file_content) { - const unsigned int header_len = strlen(GIT_SYMREF); + const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF); const char *refname_start; char *eol; refname_start = (const char *)file_content->ptr; - if (file_content->size < (header_len + 1)) - return git__throw(GIT_EOBJCORRUPTED, - "Failed to parse loose reference. Object too short"); + if (git_buf_len(file_content) < header_len + 1) + goto corrupt; /* * Assume we have already checked for the header * before calling this function */ - refname_start += header_len; ref->target.symbolic = git__strdup(refname_start); - if (ref->target.symbolic == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(ref->target.symbolic); /* remove newline at the end of file */ eol = strchr(ref->target.symbolic, '\n'); if (eol == NULL) - return git__throw(GIT_EOBJCORRUPTED, - "Failed to parse loose reference. Missing EOL"); + goto corrupt; *eol = '\0'; if (eol[-1] == '\r') eol[-1] = '\0'; - return GIT_SUCCESS; + return 0; + +corrupt: + giterr_set(GITERR_REFERENCE, "Corrupted loose reference file"); + return -1; } static int loose_parse_oid(git_oid *oid, git_buf *file_content) { - int error; char *buffer; buffer = (char *)file_content->ptr; /* File format: 40 chars (OID) + newline */ - if (file_content->size < GIT_OID_HEXSZ + 1) - return git__throw(GIT_EOBJCORRUPTED, - "Failed to parse loose reference. Reference too short"); + if (git_buf_len(file_content) < GIT_OID_HEXSZ + 1) + goto corrupt; - if ((error = git_oid_fromstr(oid, buffer)) < GIT_SUCCESS) - return git__rethrow(GIT_EOBJCORRUPTED, "Failed to parse loose reference."); + if (git_oid_fromstr(oid, buffer) < 0) + goto corrupt; buffer = buffer + GIT_OID_HEXSZ; if (*buffer == '\r') buffer++; if (*buffer != '\n') - return git__throw(GIT_EOBJCORRUPTED, - "Failed to parse loose reference. Missing EOL"); + goto corrupt; - return GIT_SUCCESS; + return 0; + +corrupt: + giterr_set(GITERR_REFERENCE, "Corrupted loose reference file"); + return -1; } static git_rtype loose_guess_rtype(const git_buf *full_path) @@ -198,7 +201,7 @@ static git_rtype loose_guess_rtype(const git_buf *full_path) type = GIT_REF_INVALID; - if (git_futils_readbuffer(&ref_file, full_path->ptr) == GIT_SUCCESS) { + if (git_futils_readbuffer(&ref_file, full_path->ptr) == 0) { if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) type = GIT_REF_SYMBOLIC; else @@ -211,15 +214,17 @@ static git_rtype loose_guess_rtype(const git_buf *full_path) static int loose_lookup(git_reference *ref) { - int error = GIT_SUCCESS, updated; + int result, updated; git_buf ref_file = GIT_BUF_INIT; - if (reference_read(&ref_file, &ref->mtime, - ref->owner->path_repository, ref->name, &updated) < GIT_SUCCESS) - return git__throw(GIT_ENOTFOUND, "Failed to lookup loose reference"); + result = reference_read(&ref_file, &ref->mtime, + ref->owner->path_repository, ref->name, &updated); + + if (result < 0) + return result; if (!updated) - return GIT_SUCCESS; + return 0; if (ref->flags & GIT_REF_SYMBOLIC) { git__free(ref->target.symbolic); @@ -230,18 +235,14 @@ static int loose_lookup(git_reference *ref) if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) { ref->flags |= GIT_REF_SYMBOLIC; - error = loose_parse_symbolic(ref, &ref_file); + result = loose_parse_symbolic(ref, &ref_file); } else { ref->flags |= GIT_REF_OID; - error = loose_parse_oid(&ref->target.oid, &ref_file); + result = loose_parse_oid(&ref->target.oid, &ref_file); } git_buf_free(&ref_file); - - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to lookup loose reference"); - - return GIT_SUCCESS; + return result; } static int loose_lookup_to_packfile( @@ -249,54 +250,59 @@ static int loose_lookup_to_packfile( git_repository *repo, const char *name) { - int error = GIT_SUCCESS; git_buf ref_file = GIT_BUF_INIT; struct packref *ref = NULL; size_t name_len; *ref_out = NULL; - error = reference_read(&ref_file, NULL, repo->path_repository, name, NULL); - if (error < GIT_SUCCESS) - goto cleanup; + if (reference_read(&ref_file, NULL, repo->path_repository, name, NULL) < 0) + return -1; name_len = strlen(name); ref = git__malloc(sizeof(struct packref) + name_len + 1); + GITERR_CHECK_ALLOC(ref); memcpy(ref->name, name, name_len); ref->name[name_len] = 0; - error = loose_parse_oid(&ref->oid, &ref_file); - if (error < GIT_SUCCESS) - goto cleanup; + if (loose_parse_oid(&ref->oid, &ref_file) < 0) { + git_buf_free(&ref_file); + git__free(ref); + return -1; + } ref->flags = GIT_PACKREF_WAS_LOOSE; *ref_out = ref; git_buf_free(&ref_file); - return GIT_SUCCESS; - -cleanup: - git_buf_free(&ref_file); - git__free(ref); - - return git__rethrow(error, "Failed to lookup loose reference"); + return 0; } static int loose_write(git_reference *ref) { git_filebuf file = GIT_FILEBUF_INIT; git_buf ref_path = GIT_BUF_INIT; - int error; struct stat st; - error = git_buf_joinpath(&ref_path, ref->owner->path_repository, ref->name); - if (error < GIT_SUCCESS) - goto unlock; + if (git_buf_joinpath(&ref_path, ref->owner->path_repository, ref->name) < 0) + return -1; + + /* Remove a possibly existing empty directory hierarchy + * which name would collide with the reference name + */ + if (git_path_isdir(git_buf_cstr(&ref_path)) && + (git_futils_rmdir_r(git_buf_cstr(&ref_path), GIT_DIRREMOVAL_ONLY_EMPTY_DIRS) < 0)) { + git_buf_free(&ref_path); + return -1; + } + + if (git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE) < 0) { + git_buf_free(&ref_path); + return -1; + } - error = git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE); - if (error < GIT_SUCCESS) - goto unlock; + git_buf_free(&ref_path); if (ref->flags & GIT_REF_OID) { char oid[GIT_OID_HEXSZ + 1]; @@ -304,29 +310,18 @@ static int loose_write(git_reference *ref) git_oid_fmt(oid, &ref->target.oid); oid[GIT_OID_HEXSZ] = '\0'; - error = git_filebuf_printf(&file, "%s\n", oid); - if (error < GIT_SUCCESS) - goto unlock; + git_filebuf_printf(&file, "%s\n", oid); - } else if (ref->flags & GIT_REF_SYMBOLIC) { /* GIT_REF_SYMBOLIC */ - error = git_filebuf_printf(&file, GIT_SYMREF "%s\n", ref->target.symbolic); + } else if (ref->flags & GIT_REF_SYMBOLIC) { + git_filebuf_printf(&file, GIT_SYMREF "%s\n", ref->target.symbolic); } else { - error = git__throw(GIT_EOBJCORRUPTED, - "Failed to write reference. Invalid reference type"); - goto unlock; + assert(0); /* don't let this happen */ } - if (p_stat(ref_path.ptr, &st) == GIT_SUCCESS) + if (p_stat(ref_path.ptr, &st) == 0) ref->mtime = st.st_mtime; - git_buf_free(&ref_path); - return git_filebuf_commit(&file, GIT_REFS_FILE_MODE); - -unlock: - git_buf_free(&ref_path); - git_filebuf_cleanup(&file); - return git__rethrow(error, "Failed to write loose reference"); } static int packed_parse_peel( @@ -340,34 +335,32 @@ static int packed_parse_peel( /* Ensure it's not the first entry of the file */ if (tag_ref == NULL) - return git__throw(GIT_EPACKEDREFSCORRUPTED, - "Failed to parse packed reference. " - "Reference is the first entry of the file"); + goto corrupt; /* Ensure reference is a tag */ if (git__prefixcmp(tag_ref->name, GIT_REFS_TAGS_DIR) != 0) - return git__throw(GIT_EPACKEDREFSCORRUPTED, - "Failed to parse packed reference. Reference is not a tag"); + goto corrupt; if (buffer + GIT_OID_HEXSZ >= buffer_end) - return git__throw(GIT_EPACKEDREFSCORRUPTED, - "Failed to parse packed reference. Buffer too small"); + goto corrupt; /* Is this a valid object id? */ - if (git_oid_fromstr(&tag_ref->peel, buffer) < GIT_SUCCESS) - return git__throw(GIT_EPACKEDREFSCORRUPTED, - "Failed to parse packed reference. Not a valid object ID"); + if (git_oid_fromstr(&tag_ref->peel, buffer) < 0) + goto corrupt; buffer = buffer + GIT_OID_HEXSZ; if (*buffer == '\r') buffer++; if (*buffer != '\n') - return git__throw(GIT_EPACKEDREFSCORRUPTED, - "Failed to parse packed reference. Buffer not terminated correctly"); + goto corrupt; *buffer_out = buffer + 1; - return GIT_SUCCESS; + return 0; + +corrupt: + giterr_set(GITERR_REFERENCE, "The packed references file is corrupted"); + return -1; } static int packed_parse_oid( @@ -380,26 +373,20 @@ static int packed_parse_oid( const char *buffer = *buffer_out; const char *refname_begin, *refname_end; - int error = GIT_SUCCESS; size_t refname_len; git_oid id; refname_begin = (buffer + GIT_OID_HEXSZ + 1); - if (refname_begin >= buffer_end || - refname_begin[-1] != ' ') { - error = GIT_EPACKEDREFSCORRUPTED; - goto cleanup; - } + if (refname_begin >= buffer_end || refname_begin[-1] != ' ') + goto corrupt; /* Is this a valid object id? */ - if ((error = git_oid_fromstr(&id, buffer)) < GIT_SUCCESS) - goto cleanup; + if (git_oid_fromstr(&id, buffer) < 0) + goto corrupt; refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin); - if (refname_end == NULL) { - error = GIT_EPACKEDREFSCORRUPTED; - goto cleanup; - } + if (refname_end == NULL) + goto corrupt; if (refname_end[-1] == '\r') refname_end--; @@ -407,6 +394,7 @@ static int packed_parse_oid( refname_len = refname_end - refname_begin; ref = git__malloc(sizeof(struct packref) + refname_len + 1); + GITERR_CHECK_ALLOC(ref); memcpy(ref->name, refname_begin, refname_len); ref->name[refname_len] = 0; @@ -418,32 +406,28 @@ static int packed_parse_oid( *ref_out = ref; *buffer_out = refname_end + 1; - return GIT_SUCCESS; + return 0; -cleanup: +corrupt: git__free(ref); - return git__rethrow(error, "Failed to parse OID of packed reference"); + giterr_set(GITERR_REFERENCE, "The packed references file is corrupted"); + return -1; } static int packed_load(git_repository *repo) { - int error = GIT_SUCCESS, updated; + int result, updated; git_buf packfile = GIT_BUF_INIT; const char *buffer_start, *buffer_end; git_refcache *ref_cache = &repo->references; /* First we make sure we have allocated the hash table */ if (ref_cache->packfile == NULL) { - ref_cache->packfile = git_hashtable_alloc( - default_table_size, git_hash__strhash_cb, git_hash__strcmp_cb); - - if (ref_cache->packfile == NULL) { - error = GIT_ENOMEM; - goto cleanup; - } + ref_cache->packfile = git_strmap_alloc(); + GITERR_CHECK_ALLOC(ref_cache->packfile); } - error = reference_read(&packfile, &ref_cache->packfile_time, + result = reference_read(&packfile, &ref_cache->packfile_time, repo->path_repository, GIT_PACKEDREFS_FILE, &updated); /* @@ -453,62 +437,59 @@ static int packed_load(git_repository *repo) * for us here, so just return. Anything else means we need to * refresh the packed refs. */ - if (error == GIT_ENOTFOUND) { - git_hashtable_clear(ref_cache->packfile); - return GIT_SUCCESS; - } else if (error < GIT_SUCCESS) { - return git__rethrow(error, "Failed to read packed refs"); - } else if (!updated) { - return GIT_SUCCESS; + if (result == GIT_ENOTFOUND) { + git_strmap_clear(ref_cache->packfile); + return 0; } + if (result < 0) + return -1; + + if (!updated) + return 0; + /* * At this point, we want to refresh the packed refs. We already * have the contents in our buffer. */ - - git_hashtable_clear(ref_cache->packfile); + git_strmap_clear(ref_cache->packfile); buffer_start = (const char *)packfile.ptr; buffer_end = (const char *)(buffer_start) + packfile.size; while (buffer_start < buffer_end && buffer_start[0] == '#') { buffer_start = strchr(buffer_start, '\n'); - if (buffer_start == NULL) { - error = GIT_EPACKEDREFSCORRUPTED; - goto cleanup; - } + if (buffer_start == NULL) + goto parse_failed; + buffer_start++; } while (buffer_start < buffer_end) { + int err; struct packref *ref = NULL; - error = packed_parse_oid(&ref, &buffer_start, buffer_end); - if (error < GIT_SUCCESS) - goto cleanup; + if (packed_parse_oid(&ref, &buffer_start, buffer_end) < 0) + goto parse_failed; if (buffer_start[0] == '^') { - error = packed_parse_peel(ref, &buffer_start, buffer_end); - if (error < GIT_SUCCESS) - goto cleanup; + if (packed_parse_peel(ref, &buffer_start, buffer_end) < 0) + goto parse_failed; } - error = git_hashtable_insert(ref_cache->packfile, ref->name, ref); - if (error < GIT_SUCCESS) { - git__free(ref); - goto cleanup; - } + git_strmap_insert(ref_cache->packfile, ref->name, ref, err); + if (err < 0) + goto parse_failed; } git_buf_free(&packfile); - return GIT_SUCCESS; + return 0; -cleanup: - git_hashtable_free(ref_cache->packfile); +parse_failed: + git_strmap_free(ref_cache->packfile); ref_cache->packfile = NULL; git_buf_free(&packfile); - return git__rethrow(error, "Failed to load packed references"); + return -1; } @@ -526,17 +507,17 @@ static int _dirent_loose_listall(void *_data, git_buf *full_path) struct dirent_list_data *data = (struct dirent_list_data *)_data; const char *file_path = full_path->ptr + data->repo_path_len; - if (git_path_isdir(full_path->ptr) == GIT_SUCCESS) + if (git_path_isdir(full_path->ptr) == true) return git_path_direach(full_path, _dirent_loose_listall, _data); /* do not add twice a reference that exists already in the packfile */ if ((data->list_flags & GIT_REF_PACKED) != 0 && - git_hashtable_lookup(data->repo->references.packfile, file_path) != NULL) - return GIT_SUCCESS; + git_strmap_exists(data->repo->references.packfile, file_path)) + return 0; if (data->list_flags != GIT_REF_LISTALL) { if ((data->list_flags & loose_guess_rtype(full_path)) == 0) - return GIT_SUCCESS; /* we are filtering out this reference */ + return 0; /* we are filtering out this reference */ } return data->callback(file_path, data->callback_payload); @@ -548,30 +529,25 @@ static int _dirent_loose_load(void *data, git_buf *full_path) void *old_ref = NULL; struct packref *ref; const char *file_path; - int error; + int err; - if (git_path_isdir(full_path->ptr) == GIT_SUCCESS) + if (git_path_isdir(full_path->ptr) == true) return git_path_direach(full_path, _dirent_loose_load, repository); file_path = full_path->ptr + strlen(repository->path_repository); - error = loose_lookup_to_packfile(&ref, repository, file_path); - if (error == GIT_SUCCESS) { + if (loose_lookup_to_packfile(&ref, repository, file_path) < 0) + return -1; - if (git_hashtable_insert2( - repository->references.packfile, - ref->name, ref, &old_ref) < GIT_SUCCESS) { - git__free(ref); - return GIT_ENOMEM; - } - - if (old_ref != NULL) - git__free(old_ref); + git_strmap_insert2( + repository->references.packfile, ref->name, ref, old_ref, err); + if (err < 0) { + git__free(ref); + return -1; } - return error == GIT_SUCCESS ? - GIT_SUCCESS : - git__rethrow(error, "Failed to load loose references into packfile"); + git__free(old_ref); + return 0; } /* @@ -582,24 +558,24 @@ static int _dirent_loose_load(void *data, git_buf *full_path) */ static int packed_loadloose(git_repository *repository) { - int error = GIT_SUCCESS; git_buf refs_path = GIT_BUF_INIT; + int result; /* the packfile must have been previously loaded! */ assert(repository->references.packfile); - if ((error = git_buf_joinpath(&refs_path, - repository->path_repository, GIT_REFS_DIR)) < GIT_SUCCESS) - return error; + if (git_buf_joinpath(&refs_path, repository->path_repository, GIT_REFS_DIR) < 0) + return -1; /* * Load all the loose files from disk into the Packfile table. * This will overwrite any old packed entries with their * updated loose versions */ - error = git_path_direach(&refs_path, _dirent_loose_load, repository); + result = git_path_direach(&refs_path, _dirent_loose_load, repository); git_buf_free(&refs_path); - return error; + + return result; } /* @@ -607,7 +583,6 @@ static int packed_loadloose(git_repository *repository) */ static int packed_write_ref(struct packref *ref, git_filebuf *file) { - int error; char oid[GIT_OID_HEXSZ + 1]; git_oid_fmt(oid, &ref->oid); @@ -628,14 +603,14 @@ static int packed_write_ref(struct packref *ref, git_filebuf *file) git_oid_fmt(peel, &ref->peel); peel[GIT_OID_HEXSZ] = 0; - error = git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel); + if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0) + return -1; } else { - error = git_filebuf_printf(file, "%s %s\n", oid, ref->name); + if (git_filebuf_printf(file, "%s %s\n", oid, ref->name) < 0) + return -1; } - return error == GIT_SUCCESS ? - GIT_SUCCESS : - git__rethrow(error, "Failed to write packed reference"); + return 0; } /* @@ -649,24 +624,22 @@ static int packed_write_ref(struct packref *ref, git_filebuf *file) static int packed_find_peel(git_repository *repo, struct packref *ref) { git_object *object; - int error; if (ref->flags & GIT_PACKREF_HAS_PEEL) - return GIT_SUCCESS; + return 0; /* * Only applies to tags, i.e. references * in the /refs/tags folder */ if (git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) != 0) - return GIT_SUCCESS; + return 0; /* * Find the tagged object in the repository */ - error = git_object_lookup(&object, repo, &ref->oid, GIT_OBJ_ANY); - if (error < GIT_SUCCESS) - return git__throw(GIT_EOBJCORRUPTED, "Failed to find packed reference"); + if (git_object_lookup(&object, repo, &ref->oid, GIT_OBJ_ANY) < 0) + return -1; /* * If the tagged object is a Tag object, we need to resolve it; @@ -690,7 +663,7 @@ static int packed_find_peel(git_repository *repo, struct packref *ref) } git_object_free(object); - return GIT_SUCCESS; + return 0; } /* @@ -708,25 +681,27 @@ static int packed_remove_loose(git_repository *repo, git_vector *packing_list) { unsigned int i; git_buf full_path = GIT_BUF_INIT; - int error = GIT_SUCCESS; + int failed = 0; for (i = 0; i < packing_list->length; ++i) { struct packref *ref = git_vector_get(packing_list, i); - int an_error; if ((ref->flags & GIT_PACKREF_WAS_LOOSE) == 0) continue; - an_error = git_buf_joinpath(&full_path, repo->path_repository, ref->name); + if (git_buf_joinpath(&full_path, repo->path_repository, ref->name) < 0) + return -1; /* critical; do not try to recover on oom */ - if (an_error == GIT_SUCCESS && - git_path_exists(full_path.ptr) == GIT_SUCCESS && - p_unlink(full_path.ptr) < GIT_SUCCESS) - an_error = GIT_EOSERR; + if (git_path_exists(full_path.ptr) == true && p_unlink(full_path.ptr) < 0) { + if (failed) + continue; - /* keep the error if we haven't seen one yet */ - if (error > an_error) - error = an_error; + giterr_set(GITERR_REFERENCE, + "Failed to remove loose reference '%s' after packing: %s", + full_path.ptr, strerror(errno)); + + failed = 1; + } /* * if we fail to remove a single file, this is *not* good, @@ -737,10 +712,7 @@ static int packed_remove_loose(git_repository *repo, git_vector *packing_list) } git_buf_free(&full_path); - - return error == GIT_SUCCESS ? - GIT_SUCCESS : - git__rethrow(error, "Failed to remove loose packed reference"); + return failed ? -1 : 0; } static int packed_sort(const void *a, const void *b) @@ -757,176 +729,228 @@ static int packed_sort(const void *a, const void *b) static int packed_write(git_repository *repo) { git_filebuf pack_file = GIT_FILEBUF_INIT; - int error; - const char *errmsg = "Failed to write packed references file"; unsigned int i; git_buf pack_file_path = GIT_BUF_INIT; git_vector packing_list; - size_t total_refs; + unsigned int total_refs; assert(repo && repo->references.packfile); - total_refs = repo->references.packfile->key_count; - if ((error = - git_vector_init(&packing_list, total_refs, packed_sort)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to init packed references list"); + total_refs = + (unsigned int)git_strmap_num_entries(repo->references.packfile); + + if (git_vector_init(&packing_list, total_refs, packed_sort) < 0) + return -1; /* Load all the packfile into a vector */ { struct packref *reference; - GIT_HASHTABLE_FOREACH_VALUE(repo->references.packfile, reference, - /* cannot fail: vector already has the right size */ + /* cannot fail: vector already has the right size */ + git_strmap_foreach_value(repo->references.packfile, reference, { git_vector_insert(&packing_list, reference); - ); + }); } /* sort the vector so the entries appear sorted on the packfile */ git_vector_sort(&packing_list); /* Now we can open the file! */ - error = git_buf_joinpath(&pack_file_path, repo->path_repository, GIT_PACKEDREFS_FILE); - if (error < GIT_SUCCESS) - goto cleanup; + if (git_buf_joinpath(&pack_file_path, repo->path_repository, GIT_PACKEDREFS_FILE) < 0) + goto cleanup_memory; - if ((error = git_filebuf_open(&pack_file, pack_file_path.ptr, 0)) < GIT_SUCCESS) { - errmsg = "Failed to open packed references file"; - goto cleanup; - } + if (git_filebuf_open(&pack_file, pack_file_path.ptr, 0) < 0) + goto cleanup_packfile; /* Packfiles have a header... apparently * This is in fact not required, but we might as well print it * just for kicks */ - if ((error = - git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER)) < GIT_SUCCESS) { - errmsg = "Failed to write packed references file header"; - goto cleanup; - } + if (git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER) < 0) + goto cleanup_packfile; for (i = 0; i < packing_list.length; ++i) { struct packref *ref = (struct packref *)git_vector_get(&packing_list, i); - if ((error = packed_find_peel(repo, ref)) < GIT_SUCCESS) { - error = git__throw(GIT_EOBJCORRUPTED, - "A reference cannot be peeled"); - goto cleanup; - } + if (packed_find_peel(repo, ref) < 0) + goto cleanup_packfile; - if ((error = packed_write_ref(ref, &pack_file)) < GIT_SUCCESS) - goto cleanup; + if (packed_write_ref(ref, &pack_file) < 0) + goto cleanup_packfile; } -cleanup: /* if we've written all the references properly, we can commit * the packfile to make the changes effective */ - if (error == GIT_SUCCESS) { - error = git_filebuf_commit(&pack_file, GIT_PACKEDREFS_FILE_MODE); + if (git_filebuf_commit(&pack_file, GIT_PACKEDREFS_FILE_MODE) < 0) + goto cleanup_memory; - /* when and only when the packfile has been properly written, - * we can go ahead and remove the loose refs */ - if (error == GIT_SUCCESS) { - struct stat st; + /* when and only when the packfile has been properly written, + * we can go ahead and remove the loose refs */ + if (packed_remove_loose(repo, &packing_list) < 0) + goto cleanup_memory; - error = packed_remove_loose(repo, &packing_list); - - if (p_stat(pack_file_path.ptr, &st) == GIT_SUCCESS) - repo->references.packfile_time = st.st_mtime; - } - } - else git_filebuf_cleanup(&pack_file); + { + struct stat st; + if (p_stat(pack_file_path.ptr, &st) == 0) + repo->references.packfile_time = st.st_mtime; + } git_vector_free(&packing_list); git_buf_free(&pack_file_path); - if (error < GIT_SUCCESS) - git__rethrow(error, "%s", errmsg); + /* we're good now */ + return 0; + +cleanup_packfile: + git_filebuf_cleanup(&pack_file); + +cleanup_memory: + git_vector_free(&packing_list); + git_buf_free(&pack_file_path); - return error; + return -1; } +struct reference_available_t { + const char *new_ref; + const char *old_ref; + int available; +}; + static int _reference_available_cb(const char *ref, void *data) { - const char *new, *old; - const char **refs; + struct reference_available_t *d; assert(ref && data); + d = (struct reference_available_t *)data; - refs = (const char **)data; - - new = (const char *)refs[0]; - old = (const char *)refs[1]; + if (!d->old_ref || strcmp(d->old_ref, ref)) { + size_t reflen = strlen(ref); + size_t newlen = strlen(d->new_ref); + size_t cmplen = reflen < newlen ? reflen : newlen; + const char *lead = reflen < newlen ? d->new_ref : ref; - if (!old || strcmp(old, ref)) { - int reflen = strlen(ref); - int newlen = strlen(new); - int cmplen = reflen < newlen ? reflen : newlen; - const char *lead = reflen < newlen ? new : ref; - - if (!strncmp(new, ref, cmplen) && - lead[cmplen] == '/') - return GIT_EEXISTS; + if (!strncmp(d->new_ref, ref, cmplen) && lead[cmplen] == '/') { + d->available = 0; + return -1; + } } - return GIT_SUCCESS; + return 0; } -static int reference_available( +static int reference_path_available( git_repository *repo, const char *ref, const char* old_ref) { - const char *refs[2]; + struct reference_available_t data; - refs[0] = ref; - refs[1] = old_ref; + data.new_ref = ref; + data.old_ref = old_ref; + data.available = 1; if (git_reference_foreach(repo, GIT_REF_LISTALL, - _reference_available_cb, (void *)refs) < 0) { - return git__throw(GIT_EEXISTS, - "Reference name `%s` conflicts with existing reference", ref); + _reference_available_cb, (void *)&data) < 0) + return -1; + + if (!data.available) { + giterr_set(GITERR_REFERENCE, + "The path to reference '%s' collides with an existing one", ref); + return -1; } - return GIT_SUCCESS; + return 0; } static int reference_exists(int *exists, git_repository *repo, const char *ref_name) { - int error; git_buf ref_path = GIT_BUF_INIT; - error = packed_load(repo); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Cannot resolve if a reference exists"); + if (packed_load(repo) < 0) + return -1; - error = git_buf_joinpath(&ref_path, repo->path_repository, ref_name); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Cannot resolve if a reference exists"); + if (git_buf_joinpath(&ref_path, repo->path_repository, ref_name) < 0) + return -1; - if (git_path_isfile(ref_path.ptr) == GIT_SUCCESS || - git_hashtable_lookup(repo->references.packfile, ref_path.ptr) != NULL) { + if (git_path_isfile(ref_path.ptr) == true || + git_strmap_exists(repo->references.packfile, ref_path.ptr)) + { *exists = 1; } else { *exists = 0; } git_buf_free(&ref_path); + return 0; +} + +/* + * Check if a reference could be written to disk, based on: + * + * - Whether a reference with the same name already exists, + * and we are allowing or disallowing overwrites + * + * - Whether the name of the reference would collide with + * an existing path + */ +static int reference_can_write( + git_repository *repo, + const char *refname, + const char *previous_name, + int force) +{ + /* see if the reference shares a path with an existing reference; + * if a path is shared, we cannot create the reference, even when forcing */ + if (reference_path_available(repo, refname, previous_name) < 0) + return -1; + + /* check if the reference actually exists, but only if we are not forcing + * the rename. If we are forcing, it's OK to overwrite */ + if (!force) { + int exists; + + if (reference_exists(&exists, repo, refname) < 0) + return -1; + + /* We cannot proceed if the reference already exists and we're not forcing + * the rename; the existing one would be overwritten */ + if (exists) { + giterr_set(GITERR_REFERENCE, + "A reference with that name (%s) already exists", refname); + return GIT_EEXISTS; + } + } - return GIT_SUCCESS; + /* FIXME: if the reference exists and we are forcing, do we really need to + * remove the reference first? + * + * Two cases: + * + * - the reference already exists and is loose: not a problem, the file + * gets overwritten on disk + * + * - the reference already exists and is packed: we write a new one as + * loose, which by all means renders the packed one useless + */ + + return 0; } + static int packed_lookup(git_reference *ref) { - int error; struct packref *pack_ref = NULL; + git_strmap *packfile_refs; + khiter_t pos; - error = packed_load(ref->owner); - if (error < GIT_SUCCESS) - return git__rethrow(error, - "Failed to lookup reference from packfile"); + if (packed_load(ref->owner) < 0) + return -1; - if (ref->flags & GIT_REF_PACKED && + /* maybe the packfile hasn't changed at all, so we don't + * have to re-lookup the reference */ + if ((ref->flags & GIT_REF_PACKED) && ref->mtime == ref->owner->references.packfile_time) - return GIT_SUCCESS; + return 0; if (ref->flags & GIT_REF_SYMBOLIC) { git__free(ref->target.symbolic); @@ -934,39 +958,42 @@ static int packed_lookup(git_reference *ref) } /* Look up on the packfile */ - pack_ref = git_hashtable_lookup(ref->owner->references.packfile, ref->name); - if (pack_ref == NULL) - return git__throw(GIT_ENOTFOUND, - "Failed to lookup reference from packfile"); + packfile_refs = ref->owner->references.packfile; + pos = git_strmap_lookup_index(packfile_refs, ref->name); + if (!git_strmap_valid_index(packfile_refs, pos)) { + giterr_set(GITERR_REFERENCE, "Reference '%s' not found", ref->name); + return GIT_ENOTFOUND; + } + + pack_ref = git_strmap_value_at(packfile_refs, pos); ref->flags = GIT_REF_OID | GIT_REF_PACKED; ref->mtime = ref->owner->references.packfile_time; git_oid_cpy(&ref->target.oid, &pack_ref->oid); - return GIT_SUCCESS; + return 0; } static int reference_lookup(git_reference *ref) { - int error_loose, error_packed; + int result; - error_loose = loose_lookup(ref); - if (error_loose == GIT_SUCCESS) - return GIT_SUCCESS; + result = loose_lookup(ref); + if (result == 0) + return 0; - error_packed = packed_lookup(ref); - if (error_packed == GIT_SUCCESS) - return GIT_SUCCESS; + /* only try to lookup this reference on the packfile if it + * wasn't found on the loose refs; not if there was a critical error */ + if (result == GIT_ENOTFOUND) { + giterr_clear(); + result = packed_lookup(ref); + if (result == 0) + return 0; + } + /* unexpected error; free the reference */ git_reference_free(ref); - - if (error_loose != GIT_ENOTFOUND) - return git__rethrow(error_loose, "Failed to lookup reference"); - - if (error_packed != GIT_ENOTFOUND) - return git__rethrow(error_packed, "Failed to lookup reference"); - - return git__throw(GIT_ENOTFOUND, "Reference not found"); + return result; } /* @@ -976,7 +1003,7 @@ static int reference_lookup(git_reference *ref) */ static int reference_delete(git_reference *ref) { - int error; + int result; assert(ref); @@ -984,17 +1011,28 @@ static int reference_delete(git_reference *ref) * We need to reload the packfile, remove the reference from the * packing list, and repack */ if (ref->flags & GIT_REF_PACKED) { + git_strmap *packfile_refs; struct packref *packref; + khiter_t pos; + /* load the existing packfile */ - if ((error = packed_load(ref->owner)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to delete reference"); + if (packed_load(ref->owner) < 0) + return -1; + + packfile_refs = ref->owner->references.packfile; + pos = git_strmap_lookup_index(packfile_refs, ref->name); + if (!git_strmap_valid_index(packfile_refs, pos)) { + giterr_set(GITERR_REFERENCE, + "Reference %s stopped existing in the packfile", ref->name); + return -1; + } - if (git_hashtable_remove2(ref->owner->references.packfile, - ref->name, (void **) &packref) < GIT_SUCCESS) - return git__throw(GIT_ENOTFOUND, "Reference not found"); + packref = git_strmap_value_at(packfile_refs, pos); + git_strmap_delete_at(packfile_refs, pos); - git__free (packref); - error = packed_write(ref->owner); + git__free(packref); + if (packed_write(ref->owner) < 0) + return -1; /* If the reference is loose, we can just remove the reference * from the filesystem */ @@ -1002,66 +1040,114 @@ static int reference_delete(git_reference *ref) git_reference *ref_in_pack; git_buf full_path = GIT_BUF_INIT; - error = git_buf_joinpath(&full_path, ref->owner->path_repository, ref->name); - if (error < GIT_SUCCESS) - goto cleanup; + if (git_buf_joinpath(&full_path, ref->owner->path_repository, ref->name) < 0) + return -1; - error = p_unlink(full_path.ptr); + result = p_unlink(full_path.ptr); git_buf_free(&full_path); /* done with path at this point */ - if (error < GIT_SUCCESS) - goto cleanup; + + if (result < 0) { + giterr_set(GITERR_OS, "Failed to unlink '%s'", full_path.ptr); + return -1; + } /* When deleting a loose reference, we have to ensure that an older * packed version of it doesn't exist */ - if (git_reference_lookup(&ref_in_pack, ref->owner, - ref->name) == GIT_SUCCESS) { + if (git_reference_lookup(&ref_in_pack, ref->owner, ref->name) == 0) { assert((ref_in_pack->flags & GIT_REF_PACKED) != 0); - error = git_reference_delete(ref_in_pack); + return git_reference_delete(ref_in_pack); } + + giterr_clear(); } -cleanup: - return error == GIT_SUCCESS ? - GIT_SUCCESS : - git__rethrow(error, "Failed to delete reference"); + return 0; } int git_reference_delete(git_reference *ref) { - int error = reference_delete(ref); - if (error < GIT_SUCCESS) - return error; - + int result = reference_delete(ref); git_reference_free(ref); - return GIT_SUCCESS; + return result; } - int git_reference_lookup(git_reference **ref_out, git_repository *repo, const char *name) { + return git_reference_lookup_resolved(ref_out, repo, name, 0); +} + +int git_reference_name_to_oid( + git_oid *out, git_repository *repo, const char *name) +{ int error; - char normalized_name[GIT_REFNAME_MAX]; - git_reference *ref = NULL; + git_reference *ref; + + if ((error = git_reference_lookup_resolved(&ref, repo, name, -1)) < 0) + return error; + + git_oid_cpy(out, git_reference_oid(ref)); + git_reference_free(ref); + return 0; +} + +int git_reference_lookup_resolved( + git_reference **ref_out, + git_repository *repo, + const char *name, + int max_nesting) +{ + git_reference *scan; + int result, nesting; assert(ref_out && repo && name); *ref_out = NULL; - error = normalize_name(normalized_name, sizeof(normalized_name), name, 0); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to lookup reference"); + if (max_nesting > MAX_NESTING_LEVEL) + max_nesting = MAX_NESTING_LEVEL; + else if (max_nesting < 0) + max_nesting = DEFAULT_NESTING_LEVEL; - error = reference_alloc(&ref, repo, normalized_name); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to lookup reference"); + scan = git__calloc(1, sizeof(git_reference)); + GITERR_CHECK_ALLOC(scan); - error = reference_lookup(ref); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to lookup reference"); + scan->name = git__calloc(GIT_REFNAME_MAX + 1, sizeof(char)); + GITERR_CHECK_ALLOC(scan->name); - *ref_out = ref; - return GIT_SUCCESS; + if ((result = normalize_name(scan->name, GIT_REFNAME_MAX, name, 0)) < 0) { + git_reference_free(scan); + return result; + } + + scan->target.symbolic = git__strdup(scan->name); + GITERR_CHECK_ALLOC(scan->target.symbolic); + + scan->owner = repo; + scan->flags = GIT_REF_SYMBOLIC; + + for (nesting = max_nesting; + nesting >= 0 && (scan->flags & GIT_REF_SYMBOLIC) != 0; + nesting--) + { + if (nesting != max_nesting) + strncpy(scan->name, scan->target.symbolic, GIT_REFNAME_MAX); + + scan->mtime = 0; + + if ((result = reference_lookup(scan)) < 0) + return result; /* lookup git_reference_free on scan already */ + } + + if ((scan->flags & GIT_REF_OID) == 0 && max_nesting != 0) { + giterr_set(GITERR_REFERENCE, + "Cannot resolve reference (>%u levels deep)", max_nesting); + git_reference_free(scan); + return -1; + } + + *ref_out = scan; + return 0; } /** @@ -1126,43 +1212,32 @@ int git_reference_create_symbolic( int force) { char normalized[GIT_REFNAME_MAX]; - int ref_exists, error = GIT_SUCCESS; git_reference *ref = NULL; - error = normalize_name(normalized, sizeof(normalized), name, 0); - if (error < GIT_SUCCESS) - goto cleanup; + if (normalize_name(normalized, sizeof(normalized), name, 0) < 0) + return -1; - if ((error = reference_exists(&ref_exists, repo, normalized) < GIT_SUCCESS)) - return git__rethrow(error, "Failed to create symbolic reference"); + if (reference_can_write(repo, normalized, NULL, force) < 0) + return -1; - if (ref_exists && !force) - return git__throw(GIT_EEXISTS, - "Failed to create symbolic reference. Reference already exists"); - - error = reference_alloc(&ref, repo, normalized); - if (error < GIT_SUCCESS) - goto cleanup; + if (reference_alloc(&ref, repo, normalized) < 0) + return -1; ref->flags |= GIT_REF_SYMBOLIC; /* set the target; this will normalize the name automatically * and write the reference on disk */ - error = git_reference_set_target(ref, target); - if (error < GIT_SUCCESS) - goto cleanup; - + if (git_reference_set_target(ref, target) < 0) { + git_reference_free(ref); + return -1; + } if (ref_out == NULL) { git_reference_free(ref); } else { *ref_out = ref; } - return GIT_SUCCESS; - -cleanup: - git_reference_free(ref); - return git__rethrow(error, "Failed to create symbolic reference"); + return 0; } int git_reference_create_oid( @@ -1172,34 +1247,25 @@ int git_reference_create_oid( const git_oid *id, int force) { - int error = GIT_SUCCESS, ref_exists; git_reference *ref = NULL; char normalized[GIT_REFNAME_MAX]; - error = normalize_name(normalized, sizeof(normalized), name, 1); - if (error < GIT_SUCCESS) - goto cleanup; - - if ((error = reference_exists(&ref_exists, repo, normalized) < GIT_SUCCESS)) - return git__rethrow(error, "Failed to create OID reference"); + if (normalize_name(normalized, sizeof(normalized), name, 1) < 0) + return -1; - if (ref_exists && !force) - return git__throw(GIT_EEXISTS, - "Failed to create OID reference. Reference already exists"); + if (reference_can_write(repo, normalized, NULL, force) < 0) + return -1; - if ((error = reference_available(repo, name, NULL)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to create reference"); - - error = reference_alloc(&ref, repo, name); - if (error < GIT_SUCCESS) - goto cleanup; + if (reference_alloc(&ref, repo, name) < 0) + return -1; ref->flags |= GIT_REF_OID; /* set the oid; this will write the reference on disk */ - error = git_reference_set_oid(ref, id); - if (error < GIT_SUCCESS) - goto cleanup; + if (git_reference_set_oid(ref, id) < 0) { + git_reference_free(ref); + return -1; + } if (ref_out == NULL) { git_reference_free(ref); @@ -1207,13 +1273,8 @@ int git_reference_create_oid( *ref_out = ref; } - return GIT_SUCCESS; - -cleanup: - git_reference_free(ref); - return git__rethrow(error, "Failed to create reference OID"); + return 0; } - /* * Change the OID target of a reference. * @@ -1225,38 +1286,31 @@ cleanup: */ int git_reference_set_oid(git_reference *ref, const git_oid *id) { - int error = GIT_SUCCESS, exists; git_odb *odb = NULL; - if ((ref->flags & GIT_REF_OID) == 0) - return git__throw(GIT_EINVALIDREFSTATE, - "Failed to set OID target of reference. Not an OID reference"); + if ((ref->flags & GIT_REF_OID) == 0) { + giterr_set(GITERR_REFERENCE, "Cannot set OID on symbolic reference"); + return -1; + } assert(ref->owner); - error = git_repository_odb__weakptr(&odb, ref->owner); - if (error < GIT_SUCCESS) - return error; - - exists = git_odb_exists(odb, id); - - git_odb_free(odb); + if (git_repository_odb__weakptr(&odb, ref->owner) < 0) + return -1; /* Don't let the user create references to OIDs that * don't exist in the ODB */ - if (!exists) - return git__throw(GIT_ENOTFOUND, - "Failed to set OID target of reference. OID doesn't exist in ODB"); + if (!git_odb_exists(odb, id)) { + giterr_set(GITERR_REFERENCE, + "Target OID for the reference doesn't exist on the repository"); + return -1; + } /* Update the OID value on `ref` */ git_oid_cpy(&ref->target.oid, id); /* Write back to disk */ - error = loose_write(ref); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to set OID target of reference"); - - return GIT_SUCCESS; + return loose_write(ref); } /* @@ -1268,82 +1322,44 @@ int git_reference_set_oid(git_reference *ref, const git_oid *id) */ int git_reference_set_target(git_reference *ref, const char *target) { - int error; char normalized[GIT_REFNAME_MAX]; - if ((ref->flags & GIT_REF_SYMBOLIC) == 0) - return git__throw(GIT_EINVALIDREFSTATE, - "Failed to set reference target. Not a symbolic reference"); + if ((ref->flags & GIT_REF_SYMBOLIC) == 0) { + giterr_set(GITERR_REFERENCE, + "Cannot set symbolic target on a direct reference"); + return -1; + } - error = normalize_name(normalized, sizeof(normalized), target, 0); - if (error < GIT_SUCCESS) - return git__rethrow(error, - "Failed to set reference target. Invalid target name"); + if (normalize_name(normalized, sizeof(normalized), target, 0)) + return -1; git__free(ref->target.symbolic); ref->target.symbolic = git__strdup(normalized); - if (ref->target.symbolic == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(ref->target.symbolic); return loose_write(ref); } int git_reference_rename(git_reference *ref, const char *new_name, int force) { - int error; + int result; git_buf aux_path = GIT_BUF_INIT; char normalized[GIT_REFNAME_MAX]; const char *head_target = NULL; - git_reference *existing_ref = NULL, *head = NULL; - - error = normalize_name(normalized, sizeof(normalized), - new_name, ref->flags & GIT_REF_OID); + git_reference *head = NULL; - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to rename reference. Invalid name"); + if (normalize_name(normalized, sizeof(normalized), + new_name, ref->flags & GIT_REF_OID) < 0) + return -1; - new_name = normalized; - - /* If we are forcing the rename, try to lookup a reference with the - * new one. If the lookup succeeds, we need to delete that ref - * before the renaming can proceed */ - if (force) { - error = git_reference_lookup(&existing_ref, ref->owner, new_name); - - if (error == GIT_SUCCESS) { - error = git_reference_delete(existing_ref); - if (error < GIT_SUCCESS) - return git__rethrow(error, - "Failed to rename reference. " - "The existing reference cannot be deleted"); - } else if (error != GIT_ENOTFOUND) - goto cleanup; - - /* If we're not forcing the rename, check if the reference exists. - * If it does, renaming cannot continue */ - } else { - int exists; - - error = reference_exists(&exists, ref->owner, normalized); - if (error < GIT_SUCCESS) - goto cleanup; - - if (exists) - return git__throw(GIT_EEXISTS, - "Failed to rename reference. Reference already exists"); - } - - if ((error = reference_available(ref->owner, new_name, ref->name)) < GIT_SUCCESS) - return git__rethrow(error, - "Failed to rename reference. Reference already exists"); + if (reference_can_write(ref->owner, normalized, ref->name, force) < 0) + return -1; /* Initialize path now so we won't get an allocation failure once - * we actually start removing things. - */ - error = git_buf_joinpath(&aux_path, ref->owner->path_repository, new_name); - if (error < GIT_SUCCESS) - goto cleanup; + * we actually start removing things. */ + if (git_buf_joinpath(&aux_path, ref->owner->path_repository, new_name) < 0) + return -1; /* * Now delete the old ref and remove an possibly existing directory @@ -1351,57 +1367,54 @@ int git_reference_rename(git_reference *ref, const char *new_name, int force) * method deletes the ref from disk but doesn't free the pointer, so * we can still access the ref's attributes for creating the new one */ - if ((error = reference_delete(ref)) < GIT_SUCCESS) + if (reference_delete(ref) < 0) goto cleanup; - if (git_path_exists(aux_path.ptr) == GIT_SUCCESS) { - if (git_path_isdir(aux_path.ptr) == GIT_SUCCESS) { - if ((error = git_futils_rmdir_r(aux_path.ptr, 0)) < GIT_SUCCESS) - goto rollback; - } else goto rollback; - } - /* * Finally we can create the new reference. */ if (ref->flags & GIT_REF_SYMBOLIC) { - error = git_reference_create_symbolic( - NULL, ref->owner, new_name, ref->target.symbolic, 0); + result = git_reference_create_symbolic( + NULL, ref->owner, new_name, ref->target.symbolic, force); } else { - error = git_reference_create_oid( - NULL, ref->owner, new_name, &ref->target.oid, 0); + result = git_reference_create_oid( + NULL, ref->owner, new_name, &ref->target.oid, force); } - if (error < GIT_SUCCESS) + if (result < 0) goto rollback; /* * Check if we have to update HEAD. */ - error = git_reference_lookup(&head, ref->owner, GIT_HEAD_FILE); - if (error < GIT_SUCCESS) + if (git_reference_lookup(&head, ref->owner, GIT_HEAD_FILE) < 0) { + giterr_set(GITERR_REFERENCE, + "Failed to update HEAD after renaming reference"); goto cleanup; + } head_target = git_reference_target(head); if (head_target && !strcmp(head_target, ref->name)) { - error = git_reference_create_symbolic( - &head, ref->owner, "HEAD", new_name, 1); - - if (error < GIT_SUCCESS) + if (git_reference_create_symbolic(&head, ref->owner, "HEAD", new_name, 1) < 0) { + giterr_set(GITERR_REFERENCE, + "Failed to update HEAD after renaming reference"); goto cleanup; + } } /* * Rename the reflog file. */ - error = git_buf_join_n(&aux_path, '/', 3, ref->owner->path_repository, - GIT_REFLOG_DIR, ref->name); - if (error < GIT_SUCCESS) + if (git_buf_join_n(&aux_path, '/', 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name) < 0) goto cleanup; - if (git_path_exists(aux_path.ptr) == GIT_SUCCESS) - error = git_reflog_rename(ref, new_name); + if (git_path_exists(aux_path.ptr) == true) { + if (git_reflog_rename(ref, new_name) < 0) + goto cleanup; + } else { + giterr_clear(); + } /* * Change the name of the reference given by the user. @@ -1412,92 +1425,49 @@ int git_reference_rename(git_reference *ref, const char *new_name, int force) /* The reference is no longer packed */ ref->flags &= ~GIT_REF_PACKED; + git_reference_free(head); + git_buf_free(&aux_path); + return 0; + cleanup: - /* We no longer need the newly created reference nor the head */ git_reference_free(head); git_buf_free(&aux_path); - return error == GIT_SUCCESS ? - GIT_SUCCESS : - git__rethrow(error, "Failed to rename reference"); + return -1; rollback: /* - * Try to create the old reference again. + * Try to create the old reference again, ignore failures */ if (ref->flags & GIT_REF_SYMBOLIC) - error = git_reference_create_symbolic( + git_reference_create_symbolic( NULL, ref->owner, ref->name, ref->target.symbolic, 0); else - error = git_reference_create_oid( + git_reference_create_oid( NULL, ref->owner, ref->name, &ref->target.oid, 0); /* The reference is no longer packed */ ref->flags &= ~GIT_REF_PACKED; git_buf_free(&aux_path); - - return error == GIT_SUCCESS ? - git__rethrow(GIT_ERROR, "Failed to rename reference. Did rollback") : - git__rethrow(error, "Failed to rename reference. Failed to rollback"); + return -1; } int git_reference_resolve(git_reference **ref_out, git_reference *ref) { - int error, i = 0; - git_repository *repo; - - assert(ref); - - *ref_out = NULL; - repo = ref->owner; - - /* If the reference is already resolved, we need to return a - * copy. Instead of duplicating `ref`, we look it up again to - * ensure the copy is out to date */ if (ref->flags & GIT_REF_OID) return git_reference_lookup(ref_out, ref->owner, ref->name); - - /* Otherwise, keep iterating until the reference is resolved */ - for (i = 0; i < MAX_NESTING_LEVEL; ++i) { - git_reference *new_ref; - - error = git_reference_lookup(&new_ref, repo, ref->target.symbolic); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to resolve reference"); - - /* Free intermediate references, except for the original one - * we've received */ - if (i > 0) - git_reference_free(ref); - - ref = new_ref; - - /* When the reference we've just looked up is an OID, we've - * successfully resolved the symbolic ref */ - if (ref->flags & GIT_REF_OID) { - *ref_out = ref; - return GIT_SUCCESS; - } - } - - return git__throw(GIT_ENOMEM, - "Failed to resolve reference. Reference is too nested"); + else + return git_reference_lookup_resolved(ref_out, ref->owner, ref->target.symbolic, -1); } int git_reference_packall(git_repository *repo) { - int error; - - /* load the existing packfile */ - if ((error = packed_load(repo)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to pack references"); + if (packed_load(repo) < 0 || /* load the existing packfile */ + packed_loadloose(repo) < 0 || /* add all the loose refs */ + packed_write(repo) < 0) /* write back to disk */ + return -1; - /* update it in-memory with all the loose references */ - if ((error = packed_loadloose(repo)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to pack references"); - - /* write it back to disk */ - return packed_write(repo); + return 0; } int git_reference_foreach( @@ -1506,22 +1476,23 @@ int git_reference_foreach( int (*callback)(const char *, void *), void *payload) { - int error; + int result; struct dirent_list_data data; git_buf refs_path = GIT_BUF_INIT; /* list all the packed references first */ if (list_flags & GIT_REF_PACKED) { const char *ref_name; + void *ref; + GIT_UNUSED(ref); - if ((error = packed_load(repo)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to list references"); + if (packed_load(repo) < 0) + return -1; - GIT_HASHTABLE_FOREACH_KEY(repo->references.packfile, ref_name, - if ((error = callback(ref_name, payload)) < GIT_SUCCESS) - return git__throw(error, - "Failed to list references. User callback failed"); - ); + git_strmap_foreach(repo->references.packfile, ref_name, ref, { + if (callback(ref_name, payload) < 0) + return 0; + }); } /* now list the loose references, trying not to @@ -1533,15 +1504,13 @@ int git_reference_foreach( data.callback = callback; data.callback_payload = payload; - if ((error = git_buf_joinpath(&refs_path, - repo->path_repository, GIT_REFS_DIR)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to alloc space for references"); - - error = git_path_direach(&refs_path, _dirent_loose_listall, &data); + if (git_buf_joinpath(&refs_path, repo->path_repository, GIT_REFS_DIR) < 0) + return -1; + result = git_path_direach(&refs_path, _dirent_loose_listall, &data); git_buf_free(&refs_path); - return error; + return result; } static int cb__reflist_add(const char *ref, void *data) @@ -1554,7 +1523,6 @@ int git_reference_listall( git_repository *repo, unsigned int list_flags) { - int error; git_vector ref_list; assert(array && repo); @@ -1562,33 +1530,25 @@ int git_reference_listall( array->strings = NULL; array->count = 0; - if (git_vector_init(&ref_list, 8, NULL) < GIT_SUCCESS) - return GIT_ENOMEM; + if (git_vector_init(&ref_list, 8, NULL) < 0) + return -1; - error = git_reference_foreach( - repo, list_flags, &cb__reflist_add, (void *)&ref_list); - - if (error < GIT_SUCCESS) { + if (git_reference_foreach( + repo, list_flags, &cb__reflist_add, (void *)&ref_list) < 0) { git_vector_free(&ref_list); - return error; + return -1; } array->strings = (char **)ref_list.contents; array->count = ref_list.length; - return GIT_SUCCESS; + return 0; } int git_reference_reload(git_reference *ref) { - int error = reference_lookup(ref); - - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to reload reference"); - - return GIT_SUCCESS; + return reference_lookup(ref); } - void git_repository__refcache_free(git_refcache *refs) { assert(refs); @@ -1596,10 +1556,11 @@ void git_repository__refcache_free(git_refcache *refs) if (refs->packfile) { struct packref *reference; - GIT_HASHTABLE_FOREACH_VALUE( - refs->packfile, reference, git__free(reference)); + git_strmap_foreach_value(refs->packfile, reference, { + git__free(reference); + }); - git_hashtable_free(refs->packfile); + git_strmap_free(refs->packfile); } } @@ -1643,33 +1604,26 @@ static int normalize_name( /* A refname can not be empty */ if (name_end == name) - return git__throw(GIT_EINVALIDREFNAME, - "Failed to normalize name. Reference name is empty"); + goto invalid_name; /* A refname can not end with a dot or a slash */ if (*(name_end - 1) == '.' || *(name_end - 1) == '/') - return git__throw(GIT_EINVALIDREFNAME, - "Failed to normalize name. Reference name ends with dot or slash"); + goto invalid_name; while (current < name_end && out_size) { if (!is_valid_ref_char(*current)) - return git__throw(GIT_EINVALIDREFNAME, - "Failed to normalize name. " - "Reference name contains invalid characters"); + goto invalid_name; if (buffer_out > buffer_out_start) { char prev = *(buffer_out - 1); /* A refname can not start with a dot nor contain a double dot */ if (*current == '.' && ((prev == '.') || (prev == '/'))) - return git__throw(GIT_EINVALIDREFNAME, - "Failed to normalize name. " - "Reference name starts with a dot or contains a double dot"); + goto invalid_name; /* '@{' is forbidden within a refname */ if (*current == '{' && prev == '@') - return git__throw(GIT_EINVALIDREFNAME, - "Failed to normalize name. Reference name contains '@{'"); + goto invalid_name; /* Prevent multiple slashes from being added to the output */ if (*current == '/' && prev == '/') { @@ -1686,7 +1640,7 @@ static int normalize_name( } if (!out_size) - return git__throw(GIT_EINVALIDREFNAME, "Reference name is too long"); + goto invalid_name; /* Object id refname have to contain at least one slash, except * for HEAD in a detached state or MERGE_HEAD if we're in the @@ -1696,13 +1650,11 @@ static int normalize_name( strcmp(name, GIT_HEAD_FILE) != 0 && strcmp(name, GIT_MERGE_HEAD_FILE) != 0 && strcmp(name, GIT_FETCH_HEAD_FILE) != 0) - return git__throw(GIT_EINVALIDREFNAME, - "Failed to normalize name. Reference name contains no slashes"); + goto invalid_name; /* A refname can not end with ".lock" */ if (!git__suffixcmp(name, GIT_FILELOCK_EXTENSION)) - return git__throw(GIT_EINVALIDREFNAME, - "Failed to normalize name. Reference name ends with '.lock'"); + goto invalid_name; *buffer_out = '\0'; @@ -1712,11 +1664,13 @@ static int normalize_name( */ if (is_oid_ref && !(git__prefixcmp(buffer_out_start, GIT_REFS_DIR) || strcmp(buffer_out_start, GIT_HEAD_FILE))) - return git__throw(GIT_EINVALIDREFNAME, - "Failed to normalize name. " - "Reference name does not start with 'refs/'"); + goto invalid_name; + + return 0; - return GIT_SUCCESS; +invalid_name: + giterr_set(GITERR_REFERENCE, "The given reference name is not valid"); + return -1; } int git_reference__normalize_name( @@ -1734,3 +1688,20 @@ int git_reference__normalize_name_oid( { return normalize_name(buffer_out, out_size, name, 1); } + +#define GIT_REF_TYPEMASK (GIT_REF_OID | GIT_REF_SYMBOLIC) + +int git_reference_cmp(git_reference *ref1, git_reference *ref2) +{ + assert(ref1 && ref2); + + /* let's put symbolic refs before OIDs */ + if ((ref1->flags & GIT_REF_TYPEMASK) != (ref2->flags & GIT_REF_TYPEMASK)) + return (ref1->flags & GIT_REF_SYMBOLIC) ? -1 : 1; + + if (ref1->flags & GIT_REF_SYMBOLIC) + return strcmp(ref1->target.symbolic, ref2->target.symbolic); + + return git_oid_cmp(&ref1->target.oid, &ref2->target.oid); +} + diff --git a/src/refs.h b/src/refs.h index 13b9abf15..369e91e1c 100644 --- a/src/refs.h +++ b/src/refs.h @@ -10,7 +10,7 @@ #include "common.h" #include "git2/oid.h" #include "git2/refs.h" -#include "hashtable.h" +#include "strmap.h" #define GIT_REFS_DIR "refs/" #define GIT_REFS_HEADS_DIR GIT_REFS_DIR "heads/" @@ -46,7 +46,7 @@ struct git_reference { }; typedef struct { - git_hashtable *packfile; + git_strmap *packfile; time_t packfile_time; } git_refcache; @@ -55,4 +55,27 @@ void git_repository__refcache_free(git_refcache *refs); int git_reference__normalize_name(char *buffer_out, size_t out_size, const char *name); int git_reference__normalize_name_oid(char *buffer_out, size_t out_size, const char *name); +/** + * Lookup a reference by name and try to resolve to an OID. + * + * You can control how many dereferences this will attempt to resolve the + * reference with the `max_deref` parameter, or pass -1 to use a sane + * default. If you pass 0 for `max_deref`, this will not attempt to resolve + * the reference. For any value of `max_deref` other than 0, not + * successfully resolving the reference will be reported as an error. + + * The generated reference must be freed by the user. + * + * @param reference_out Pointer to the looked-up reference + * @param repo The repository to look up the reference + * @param name The long name for the reference (e.g. HEAD, ref/heads/master, refs/tags/v0.1.0, ...) + * @param max_deref Maximum number of dereferences to make of symbolic refs, 0 means simple lookup, < 0 means use default reasonable value + * @return 0 on success or < 0 on error; not being able to resolve the reference is an error unless 0 was passed for max_deref + */ +int git_reference_lookup_resolved( + git_reference **reference_out, + git_repository *repo, + const char *name, + int max_deref); + #endif diff --git a/src/refspec.c b/src/refspec.c index a27141431..bec770a30 100644 --- a/src/refspec.c +++ b/src/refspec.c @@ -25,24 +25,21 @@ int git_refspec_parse(git_refspec *refspec, const char *str) delim = strchr(str, ':'); if (delim == NULL) { refspec->src = git__strdup(str); - if (refspec->src == NULL) - return GIT_ENOMEM; - - return GIT_SUCCESS; + GITERR_CHECK_ALLOC(refspec->src); + return 0; } refspec->src = git__strndup(str, delim - str); - if (refspec->src == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(refspec->src); refspec->dst = git__strdup(delim + 1); if (refspec->dst == NULL) { git__free(refspec->src); refspec->src = NULL; - return GIT_ENOMEM; + return -1; } - return GIT_SUCCESS; + return 0; } const char *git_refspec_src(const git_refspec *refspec) @@ -65,8 +62,10 @@ int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, con size_t baselen, namelen; baselen = strlen(spec->dst); - if (outlen <= baselen) - return git__throw(GIT_EINVALIDREFNAME, "Reference name too long"); + if (outlen <= baselen) { + giterr_set(GITERR_INVALID, "Reference name too long"); + return GIT_ESHORTBUFFER; + } /* * No '*' at the end means that it's mapped to one specific local @@ -74,7 +73,7 @@ int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, con */ if (spec->dst[baselen - 1] != '*') { memcpy(out, spec->dst, baselen + 1); /* include '\0' */ - return GIT_SUCCESS; + return 0; } /* There's a '*' at the end, so remove its length */ @@ -85,29 +84,35 @@ int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, con namelen = strlen(name); - if (outlen <= baselen + namelen) - return git__throw(GIT_EINVALIDREFNAME, "Reference name too long"); + if (outlen <= baselen + namelen) { + giterr_set(GITERR_INVALID, "Reference name too long"); + return GIT_ESHORTBUFFER; + } memcpy(out, spec->dst, baselen); memcpy(out + baselen, name, namelen + 1); - return GIT_SUCCESS; + return 0; } int git_refspec_transform_r(git_buf *out, const git_refspec *spec, const char *name) { - if (git_buf_sets(out, spec->dst) < GIT_SUCCESS) - return git_buf_lasterror(out); + if (git_buf_sets(out, spec->dst) < 0) + return -1; /* * No '*' at the end means that it's mapped to one specific local * branch, so no actual transformation is needed. */ - if (out->size > 0 && out->ptr[out->size - 1] != '*') - return GIT_SUCCESS; + if (git_buf_len(out) > 0 && out->ptr[git_buf_len(out) - 1] != '*') + return 0; - git_buf_truncate(out, out->size - 1); /* remove trailing '*' */ + git_buf_truncate(out, git_buf_len(out) - 1); /* remove trailing '*' */ git_buf_puts(out, name + strlen(spec->src) - 1); - return git_buf_lasterror(out); + if (git_buf_oom(out)) + return -1; + + return 0; } + diff --git a/src/remote.c b/src/remote.c index 52b6aacc9..98c256929 100644 --- a/src/remote.c +++ b/src/remote.c @@ -29,30 +29,26 @@ static int refspec_parse(git_refspec *refspec, const char *str) } delim = strchr(str, ':'); - if (delim == NULL) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse refspec. No ':'"); + if (delim == NULL) { + giterr_set(GITERR_NET, "Invalid refspec, missing ':'"); + return -1; + } refspec->src = git__strndup(str, delim - str); - if (refspec->src == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(refspec->src); refspec->dst = git__strdup(delim + 1); - if (refspec->dst == NULL) { - git__free(refspec->src); - refspec->src = NULL; - return GIT_ENOMEM; - } + GITERR_CHECK_ALLOC(refspec->dst); - return GIT_SUCCESS; + return 0; } static int parse_remote_refspec(git_config *cfg, git_refspec *refspec, const char *var) { - const char *val; int error; + const char *val; - error = git_config_get_string(cfg, var, &val); - if (error < GIT_SUCCESS) + if ((error = git_config_get_string(cfg, var, &val)) < 0) return error; return refspec_parse(refspec, val); @@ -66,33 +62,24 @@ int git_remote_new(git_remote **out, git_repository *repo, const char *url, cons assert(out && repo && url); remote = git__malloc(sizeof(git_remote)); - if (remote == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(remote); memset(remote, 0x0, sizeof(git_remote)); remote->repo = repo; - if (git_vector_init(&remote->refs, 32, NULL) < 0) { - git_remote_free(remote); - return GIT_ENOMEM; - } + if (git_vector_init(&remote->refs, 32, NULL) < 0) + return -1; remote->url = git__strdup(url); - if (remote->url == NULL) { - git_remote_free(remote); - return GIT_ENOMEM; - } + GITERR_CHECK_ALLOC(remote->url); if (name != NULL) { remote->name = git__strdup(name); - if (remote->name == NULL) { - git_remote_free(remote); - return GIT_ENOMEM; - } + GITERR_CHECK_ALLOC(remote->name); } *out = remote; - return GIT_SUCCESS; + return 0; } int git_remote_load(git_remote **out, git_repository *repo, const char *name) @@ -100,87 +87,68 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) git_remote *remote; git_buf buf = GIT_BUF_INIT; const char *val; - int error; + int error = 0; git_config *config; assert(out && repo && name); - error = git_repository_config__weakptr(&config, repo); - if (error < GIT_SUCCESS) - return error; + if (git_repository_config__weakptr(&config, repo) < 0) + return -1; remote = git__malloc(sizeof(git_remote)); - if (remote == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(remote); memset(remote, 0x0, sizeof(git_remote)); remote->name = git__strdup(name); - if (remote->name == NULL) { - error = GIT_ENOMEM; - goto cleanup; - } + GITERR_CHECK_ALLOC(remote->name); - if (git_vector_init(&remote->refs, 32, NULL) < 0) { - error = GIT_ENOMEM; - goto cleanup; - } + if (git_vector_init(&remote->refs, 32, NULL) < 0) + return -1; - git_buf_printf(&buf, "remote.%s.url", name); - if (git_buf_oom(&buf)) { - error = GIT_ENOMEM; - goto cleanup; - } + if (git_buf_printf(&buf, "remote.%s.url", name) < 0) + return -1; - error = git_config_get_string(config, git_buf_cstr(&buf), &val); - if (error < GIT_SUCCESS) { - error = git__rethrow(error, "Remote's url doesn't exist"); + if (git_config_get_string(config, git_buf_cstr(&buf), &val) < 0) { + error = -1; goto cleanup; } remote->repo = repo; remote->url = git__strdup(val); - if (remote->url == NULL) { - error = GIT_ENOMEM; - goto cleanup; - } + GITERR_CHECK_ALLOC(remote->url); git_buf_clear(&buf); - git_buf_printf(&buf, "remote.%s.fetch", name); - if (git_buf_oom(&buf)) { - error = GIT_ENOMEM; - goto cleanup; - } + if (git_buf_printf(&buf, "remote.%s.fetch", name) < 0) + return -1; error = parse_remote_refspec(config, &remote->fetch, git_buf_cstr(&buf)); if (error == GIT_ENOTFOUND) - error = GIT_SUCCESS; + error = 0; - if (error < GIT_SUCCESS) { - error = git__rethrow(error, "Failed to get fetch refspec"); + if (error < 0) { + error = -1; goto cleanup; } git_buf_clear(&buf); - git_buf_printf(&buf, "remote.%s.push", name); - if (git_buf_oom(&buf)) { - error = GIT_ENOMEM; - goto cleanup; - } + if (git_buf_printf(&buf, "remote.%s.push", name) < 0) + return -1; error = parse_remote_refspec(config, &remote->push, git_buf_cstr(&buf)); - /* Not finding push is fine */ if (error == GIT_ENOTFOUND) - error = GIT_SUCCESS; + error = 0; - if (error < GIT_SUCCESS) + if (error < 0) { + error = -1; goto cleanup; + } *out = remote; cleanup: git_buf_free(&buf); - if (error < GIT_SUCCESS) + if (error < 0) git_remote_free(remote); return error; @@ -188,52 +156,53 @@ cleanup: int git_remote_save(const git_remote *remote) { - int error; git_config *config; git_buf buf = GIT_BUF_INIT, value = GIT_BUF_INIT; - error = git_repository_config__weakptr(&config, remote->repo); - if (error < GIT_SUCCESS) - return error; + if (git_repository_config__weakptr(&config, remote->repo) < 0) + return -1; - git_buf_printf(&buf, "remote.%s.%s", remote->name, "url"); - if (git_buf_oom(&buf)) - return GIT_ENOMEM; + if (git_buf_printf(&buf, "remote.%s.%s", remote->name, "url") < 0) + return -1; - error = git_config_set_string(config, git_buf_cstr(&buf), remote->url); - if (error < GIT_SUCCESS) - goto cleanup; + if (git_config_set_string(config, git_buf_cstr(&buf), remote->url) < 0) { + git_buf_free(&buf); + return -1; + } if (remote->fetch.src != NULL && remote->fetch.dst != NULL) { git_buf_clear(&buf); git_buf_clear(&value); - git_buf_printf(&buf, "remote.%s.%s", remote->name, "fetch"); + git_buf_printf(&buf, "remote.%s.fetch", remote->name); git_buf_printf(&value, "%s:%s", remote->fetch.src, remote->fetch.dst); if (git_buf_oom(&buf) || git_buf_oom(&value)) - return GIT_ENOMEM; + return -1; - error = git_config_set_string(config, git_buf_cstr(&buf), git_buf_cstr(&value)); - if (error < GIT_SUCCESS) - goto cleanup; + if (git_config_set_string(config, git_buf_cstr(&buf), git_buf_cstr(&value)) < 0) + goto on_error; } if (remote->push.src != NULL && remote->push.dst != NULL) { git_buf_clear(&buf); git_buf_clear(&value); - git_buf_printf(&buf, "remote.%s.%s", remote->name, "push"); + git_buf_printf(&buf, "remote.%s.push", remote->name); git_buf_printf(&value, "%s:%s", remote->push.src, remote->push.dst); if (git_buf_oom(&buf) || git_buf_oom(&value)) - return GIT_ENOMEM; + return -1; - error = git_config_set_string(config, git_buf_cstr(&buf), git_buf_cstr(&value)); - if (error < GIT_SUCCESS) - goto cleanup; + if (git_config_set_string(config, git_buf_cstr(&buf), git_buf_cstr(&value)) < 0) + goto on_error; } -cleanup: git_buf_free(&buf); git_buf_free(&value); - return error; + + return 0; + +on_error: + git_buf_free(&buf); + git_buf_free(&value); + return -1; } const char *git_remote_name(git_remote *remote) @@ -250,21 +219,19 @@ const char *git_remote_url(git_remote *remote) int git_remote_set_fetchspec(git_remote *remote, const char *spec) { - int error; git_refspec refspec; assert(remote && spec); - error = refspec_parse(&refspec, spec); - if (error != GIT_SUCCESS) - return error; + if (refspec_parse(&refspec, spec) < 0) + return -1; git__free(remote->fetch.src); git__free(remote->fetch.dst); remote->fetch.src = refspec.src; remote->fetch.dst = refspec.dst; - return GIT_SUCCESS; + return 0; } const git_refspec *git_remote_fetchspec(git_remote *remote) @@ -275,21 +242,19 @@ const git_refspec *git_remote_fetchspec(git_remote *remote) int git_remote_set_pushspec(git_remote *remote, const char *spec) { - int error; git_refspec refspec; assert(remote && spec); - error = refspec_parse(&refspec, spec); - if (error != GIT_SUCCESS) - return error; + if (refspec_parse(&refspec, spec) < 0) + return -1; git__free(remote->push.src); git__free(remote->push.dst); remote->push.src = refspec.src; remote->push.dst = refspec.dst; - return GIT_SUCCESS; + return 0; } const git_refspec *git_remote_pushspec(git_remote *remote) @@ -300,57 +265,56 @@ const git_refspec *git_remote_pushspec(git_remote *remote) int git_remote_connect(git_remote *remote, int direction) { - int error; git_transport *t; assert(remote); - error = git_transport_new(&t, remote->url); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to create transport"); + if (git_transport_new(&t, remote->url) < 0) + return -1; - error = t->connect(t, direction); - if (error < GIT_SUCCESS) { - error = git__rethrow(error, "Failed to connect the transport"); - goto cleanup; + if (t->connect(t, direction) < 0) { + goto on_error; } remote->transport = t; -cleanup: - if (error < GIT_SUCCESS) - t->free(t); + return 0; - return error; +on_error: + t->free(t); + return -1; } int git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload) { assert(remote); - if (!remote->transport || !remote->transport->connected) - return git__throw(GIT_ERROR, "The remote is not connected"); + if (!remote->transport || !remote->transport->connected) { + giterr_set(GITERR_NET, "The remote is not connected"); + return -1; + } return remote->transport->ls(remote->transport, list_cb, payload); } -int git_remote_download(char **filename, git_remote *remote) +int git_remote_download(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats) { int error; - assert(filename && remote); + assert(remote && bytes && stats); if ((error = git_fetch_negotiate(remote)) < 0) - return git__rethrow(error, "Error negotiating"); + return error; - return git_fetch_download_pack(filename, remote); + return git_fetch_download_pack(remote, bytes, stats); } -int git_remote_update_tips(git_remote *remote) +int git_remote_update_tips(git_remote *remote, int (*cb)(const char *refname, const git_oid *a, const git_oid *b)) { - int error = GIT_SUCCESS; + int error = 0; unsigned int i = 0; git_buf refname = GIT_BUF_INIT; + git_oid old; git_vector *refs = &remote->refs; git_remote_head *head; git_reference *ref; @@ -364,31 +328,47 @@ int git_remote_update_tips(git_remote *remote) /* HEAD is only allowed to be the first in the list */ head = refs->contents[0]; if (!strcmp(head->name, GIT_HEAD_FILE)) { - error = git_reference_create_oid(&ref, remote->repo, GIT_FETCH_HEAD_FILE, &head->oid, 1); - i = 1; - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to update FETCH_HEAD"); + if (git_reference_create_oid(&ref, remote->repo, GIT_FETCH_HEAD_FILE, &head->oid, 1) < 0) + return -1; + i = 1; git_reference_free(ref); } for (; i < refs->length; ++i) { head = refs->contents[i]; - error = git_refspec_transform_r(&refname, spec, head->name); - if (error < GIT_SUCCESS) - break; + if (git_refspec_transform_r(&refname, spec, head->name) < 0) + goto on_error; + + error = git_reference_name_to_oid(&old, remote->repo, refname.ptr); + if (error < 0 && error != GIT_ENOTFOUND) + goto on_error; + + if (error == GIT_ENOTFOUND) + memset(&old, 0, GIT_OID_RAWSZ); - error = git_reference_create_oid(&ref, remote->repo, refname.ptr, &head->oid, 1); - if (error < GIT_SUCCESS) + if (!git_oid_cmp(&old, &head->oid)) + continue; + + if (git_reference_create_oid(&ref, remote->repo, refname.ptr, &head->oid, 1) < 0) break; git_reference_free(ref); + + if (cb != NULL) { + if (cb(refname.ptr, &old, &head->oid) < 0) + goto on_error; + } } git_buf_free(&refname); + return 0; + +on_error: + git_buf_free(&refname); + return -1; - return error; } int git_remote_connected(git_remote *remote) @@ -401,13 +381,8 @@ void git_remote_disconnect(git_remote *remote) { assert(remote); - if (remote->transport != NULL) { - if (remote->transport->connected) + if (remote->transport != NULL && remote->transport->connected) remote->transport->close(remote->transport); - - remote->transport->free(remote->transport); - remote->transport = NULL; - } } void git_remote_free(git_remote *remote) @@ -415,14 +390,21 @@ void git_remote_free(git_remote *remote) if (remote == NULL) return; + if (remote->transport != NULL) { + git_remote_disconnect(remote); + + remote->transport->free(remote->transport); + remote->transport = NULL; + } + + git_vector_free(&remote->refs); + git__free(remote->fetch.src); git__free(remote->fetch.dst); git__free(remote->push.src); git__free(remote->push.dst); git__free(remote->url); git__free(remote->name); - git_vector_free(&remote->refs); - git_remote_disconnect(remote); git__free(remote); } @@ -436,20 +418,17 @@ static int remote_list_cb(const char *name, const char *value, void *data_) struct cb_data *data = (struct cb_data *)data_; size_t nmatch = 2; regmatch_t pmatch[2]; - int error; GIT_UNUSED(value); if (!regexec(data->preg, name, nmatch, pmatch, 0)) { char *remote_name = git__strndup(&name[pmatch[1].rm_so], pmatch[1].rm_eo - pmatch[1].rm_so); - if (remote_name == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(remote_name); - error = git_vector_insert(data->list, remote_name); - if (error < GIT_SUCCESS) - return error; + if (git_vector_insert(data->list, remote_name) < 0) + return -1; } - return GIT_SUCCESS; + return 0; } int git_remote_list(git_strarray *remotes_list, git_repository *repo) @@ -460,27 +439,26 @@ int git_remote_list(git_strarray *remotes_list, git_repository *repo) struct cb_data data; int error; - error = git_repository_config__weakptr(&cfg, repo); - if (error < GIT_SUCCESS) - return error; + if (git_repository_config__weakptr(&cfg, repo) < 0) + return -1; - error = git_vector_init(&list, 4, NULL); - if (error < GIT_SUCCESS) - return error; + if (git_vector_init(&list, 4, NULL) < 0) + return -1; - error = regcomp(&preg, "^remote\\.(.*)\\.url$", REG_EXTENDED); - if (error < 0) - return GIT_EOSERR; + if (regcomp(&preg, "^remote\\.(.*)\\.url$", REG_EXTENDED) < 0) { + giterr_set(GITERR_OS, "Remote catch regex failed to compile"); + return -1; + } data.list = &list; data.preg = &preg; error = git_config_foreach(cfg, remote_list_cb, &data); regfree(&preg); - if (error < GIT_SUCCESS) { + if (error < 0) { size_t i; char *elem; git_vector_foreach(&list, i, elem) { - free(elem); + git__free(elem); } git_vector_free(&list); @@ -490,5 +468,5 @@ int git_remote_list(git_strarray *remotes_list, git_repository *repo) remotes_list->strings = (char **)list.contents; remotes_list->count = list.length; - return GIT_SUCCESS; + return 0; } diff --git a/src/repository.c b/src/repository.c index 1f8306991..cfabee420 100644 --- a/src/repository.c +++ b/src/repository.c @@ -5,6 +5,7 @@ * a Linking Exception. For full terms see the included COPYING file. */ #include <stdarg.h> +#include <ctype.h> #include "git2/object.h" @@ -20,7 +21,7 @@ #define GIT_OBJECTS_INFO_DIR GIT_OBJECTS_DIR "info/" #define GIT_OBJECTS_PACK_DIR GIT_OBJECTS_DIR "pack/" -#define GIT_FILE_CONTENT_PREFIX "gitdir: " +#define GIT_FILE_CONTENT_PREFIX "gitdir:" #define GIT_BRANCH_MASTER "master" @@ -64,6 +65,7 @@ void git_repository_free(git_repository *repo) git_cache_free(&repo->objects); git_repository__refcache_free(&repo->references); git_attr_cache_flush(repo); + git_submodule_config_free(repo); git__free(repo->path_repository); git__free(repo->workdir); @@ -80,35 +82,31 @@ void git_repository_free(git_repository *repo) * * Open a repository object from its path */ -static int quickcheck_repository_dir(git_buf *repository_path) +static bool valid_repository_path(git_buf *repository_path) { /* Check OBJECTS_DIR first, since it will generate the longest path name */ - if (git_path_contains_dir(repository_path, GIT_OBJECTS_DIR) < 0) - return GIT_ERROR; + if (git_path_contains_dir(repository_path, GIT_OBJECTS_DIR) == false) + return false; /* Ensure HEAD file exists */ - if (git_path_contains_file(repository_path, GIT_HEAD_FILE) < 0) - return GIT_ERROR; + if (git_path_contains_file(repository_path, GIT_HEAD_FILE) == false) + return false; - if (git_path_contains_dir(repository_path, GIT_REFS_DIR) < 0) - return GIT_ERROR; + if (git_path_contains_dir(repository_path, GIT_REFS_DIR) == false) + return false; - return GIT_SUCCESS; + return true; } - static git_repository *repository_alloc(void) { - int error; - git_repository *repo = git__malloc(sizeof(git_repository)); if (!repo) return NULL; memset(repo, 0x0, sizeof(git_repository)); - error = git_cache_init(&repo->objects, GIT_DEFAULT_CACHE_SIZE, &git_object__free); - if (error < GIT_SUCCESS) { + if (git_cache_init(&repo->objects, GIT_DEFAULT_CACHE_SIZE, &git_object__free) < 0) { git__free(repo); return NULL; } @@ -121,180 +119,385 @@ static git_repository *repository_alloc(void) static int load_config_data(git_repository *repo) { - int error, is_bare; + int is_bare; git_config *config; - error = git_repository_config__weakptr(&config, repo); - if (error < GIT_SUCCESS) + if (git_repository_config__weakptr(&config, repo) < 0) + return -1; + + if (git_config_get_bool(config, "core.bare", &is_bare) < 0) + return -1; /* FIXME: We assume that a missing core.bare + variable is an error. Is this right? */ + + repo->is_bare = is_bare; + return 0; +} + +static int load_workdir(git_repository *repo, git_buf *parent_path) +{ + int error; + git_config *config; + const char *worktree; + git_buf worktree_buf = GIT_BUF_INIT; + + if (repo->is_bare) + return 0; + + if (git_repository_config__weakptr(&config, repo) < 0) + return -1; + + error = git_config_get_string(config, "core.worktree", &worktree); + if (!error && worktree != NULL) + repo->workdir = git__strdup(worktree); + else if (error != GIT_ENOTFOUND) return error; + else { + giterr_clear(); + + if (parent_path && git_path_isdir(parent_path->ptr)) + repo->workdir = git_buf_detach(parent_path); + else { + git_path_dirname_r(&worktree_buf, repo->path_repository); + git_path_to_dir(&worktree_buf); + repo->workdir = git_buf_detach(&worktree_buf); + } + } + + GITERR_CHECK_ALLOC(repo->workdir); + + return 0; +} + +/* + * This function returns furthest offset into path where a ceiling dir + * is found, so we can stop processing the path at that point. + * + * Note: converting this to use git_bufs instead of GIT_PATH_MAX buffers on + * the stack could remove directories name limits, but at the cost of doing + * repeated malloc/frees inside the loop below, so let's not do it now. + */ +static int find_ceiling_dir_offset( + const char *path, + const char *ceiling_directories) +{ + char buf[GIT_PATH_MAX + 1]; + char buf2[GIT_PATH_MAX + 1]; + const char *ceil, *sep; + size_t len, max_len = 0, min_len; + + assert(path); + + min_len = (size_t)(git_path_root(path) + 1); + + if (ceiling_directories == NULL || min_len == 0) + return (int)min_len; + + for (sep = ceil = ceiling_directories; *sep; ceil = sep + 1) { + for (sep = ceil; *sep && *sep != GIT_PATH_LIST_SEPARATOR; sep++); + len = sep - ceil; - error = git_config_get_bool(config, "core.bare", &is_bare); - if (error == GIT_SUCCESS) - repo->is_bare = is_bare; + if (len == 0 || len >= sizeof(buf) || git_path_root(ceil) == -1) + continue; - /* TODO: what else can we load/cache here? */ + strncpy(buf, ceil, len); + buf[len] = '\0'; - return GIT_SUCCESS; + if (p_realpath(buf, buf2) == NULL) + continue; + + len = strlen(buf2); + if (len > 0 && buf2[len-1] == '/') + buf[--len] = '\0'; + + if (!strncmp(path, buf2, len) && + path[len] == '/' && + len > max_len) + { + max_len = len; + } + } + + return (int)(max_len <= min_len ? min_len : max_len); } -static int load_workdir(git_repository *repo) +/* + * Read the contents of `file_path` and set `path_out` to the repo dir that + * it points to. Before calling, set `path_out` to the base directory that + * should be used if the contents of `file_path` are a relative path. + */ +static int read_gitfile(git_buf *path_out, const char *file_path) { - int error; - git_buf workdir_buf = GIT_BUF_INIT; + int error = 0; + git_buf file = GIT_BUF_INIT; + size_t prefix_len = strlen(GIT_FILE_CONTENT_PREFIX); - if (repo->is_bare) - return GIT_SUCCESS; + assert(path_out && file_path); - git_path_dirname_r(&workdir_buf, repo->path_repository); - git_path_to_dir(&workdir_buf); + if (git_futils_readbuffer(&file, file_path) < 0) + return -1; - if ((error = git_buf_lasterror(&workdir_buf)) == GIT_SUCCESS) - repo->workdir = git_buf_detach(&workdir_buf); + git_buf_rtrim(&file); - git_buf_free(&workdir_buf); + if (file.size <= prefix_len || + memcmp(file.ptr, GIT_FILE_CONTENT_PREFIX, prefix_len) != 0) + { + giterr_set(GITERR_REPOSITORY, "The `.git` file at '%s' is malformed", file_path); + error = -1; + } + else if ((error = git_path_dirname_r(path_out, file_path)) >= 0) { + const char *gitlink = ((const char *)file.ptr) + prefix_len; + while (*gitlink && isspace(*gitlink)) gitlink++; + error = git_path_prettify_dir(path_out, gitlink, path_out->ptr); + } + git_buf_free(&file); return error; } -int git_repository_open(git_repository **repo_out, const char *path) +static int find_repo( + git_buf *repo_path, + git_buf *parent_path, + const char *start_path, + uint32_t flags, + const char *ceiling_dirs) { - int error = GIT_SUCCESS; - git_buf path_buf = GIT_BUF_INIT; - git_repository *repo = NULL; + int error; + git_buf path = GIT_BUF_INIT; + struct stat st; + dev_t initial_device = 0; + bool try_with_dot_git = false; + int ceiling_offset; + + git_buf_free(repo_path); + + if ((error = git_path_prettify_dir(&path, start_path, NULL)) < 0) + return error; - error = git_path_prettify_dir(&path_buf, path, NULL); - if (error < GIT_SUCCESS) - goto cleanup; - - /** - * Check if the path we've been given is actually the path - * of the working dir, by testing if it contains a `.git` - * folder inside of it. - */ - if (git_path_contains_dir(&path_buf, GIT_DIR) == GIT_SUCCESS) - git_buf_joinpath(&path_buf, path_buf.ptr, GIT_DIR); - - if (quickcheck_repository_dir(&path_buf) < GIT_SUCCESS) { - error = git__throw(GIT_ENOTAREPO, - "The given path (%s) is not a valid Git repository", git_buf_cstr(&path_buf)); - goto cleanup; + ceiling_offset = find_ceiling_dir_offset(path.ptr, ceiling_dirs); + + if ((error = git_buf_joinpath(&path, path.ptr, DOT_GIT)) < 0) + return error; + + while (!error && !git_buf_len(repo_path)) { + if (p_stat(path.ptr, &st) == 0) { + /* check that we have not crossed device boundaries */ + if (initial_device == 0) + initial_device = st.st_dev; + else if (st.st_dev != initial_device && + (flags & GIT_REPOSITORY_OPEN_CROSS_FS) == 0) + break; + + if (S_ISDIR(st.st_mode)) { + if (valid_repository_path(&path)) { + git_path_to_dir(&path); + git_buf_set(repo_path, path.ptr, path.size); + break; + } + } + else if (S_ISREG(st.st_mode)) { + git_buf repo_link = GIT_BUF_INIT; + + if (!(error = read_gitfile(&repo_link, path.ptr))) { + if (valid_repository_path(&repo_link)) + git_buf_swap(repo_path, &repo_link); + + git_buf_free(&repo_link); + break; + } + git_buf_free(&repo_link); + } + } + + /* move up one directory level */ + if (git_path_dirname_r(&path, path.ptr) < 0) { + error = -1; + break; + } + + if (try_with_dot_git) { + /* if we tried original dir with and without .git AND either hit + * directory ceiling or NO_SEARCH was requested, then be done. + */ + if (path.ptr[ceiling_offset] == '\0' || + (flags & GIT_REPOSITORY_OPEN_NO_SEARCH) != 0) + break; + /* otherwise look first for .git item */ + error = git_buf_joinpath(&path, path.ptr, DOT_GIT); + } + try_with_dot_git = !try_with_dot_git; } - repo = repository_alloc(); - if (repo == NULL) { - error = GIT_ENOMEM; - goto cleanup; + if (!error && parent_path != NULL) { + if (!git_buf_len(repo_path)) + git_buf_clear(parent_path); + else { + git_path_dirname_r(parent_path, path.ptr); + git_path_to_dir(parent_path); + } + if (git_buf_oom(parent_path)) + return -1; } - repo->path_repository = git_buf_detach(&path_buf); - if (repo->path_repository == NULL) { - error = GIT_ENOMEM; - goto cleanup; + git_buf_free(&path); + + if (!git_buf_len(repo_path) && !error) { + giterr_set(GITERR_REPOSITORY, + "Could not find repository from '%s'", start_path); + error = GIT_ENOTFOUND; } - error = load_config_data(repo); - if (error < GIT_SUCCESS) - goto cleanup; + return error; +} - error = load_workdir(repo); - if (error < GIT_SUCCESS) - goto cleanup; +int git_repository_open_ext( + git_repository **repo_ptr, + const char *start_path, + uint32_t flags, + const char *ceiling_dirs) +{ + int error; + git_buf path = GIT_BUF_INIT, parent = GIT_BUF_INIT; + git_repository *repo; - *repo_out = repo; - return GIT_SUCCESS; + *repo_ptr = NULL; - cleanup: - git_repository_free(repo); - git_buf_free(&path_buf); - return error; + if ((error = find_repo(&path, &parent, start_path, flags, ceiling_dirs)) < 0) + return error; + + repo = repository_alloc(); + GITERR_CHECK_ALLOC(repo); + + repo->path_repository = git_buf_detach(&path); + GITERR_CHECK_ALLOC(repo->path_repository); + + if ((error = load_config_data(repo)) < 0 || + (error = load_workdir(repo, &parent)) < 0) + { + git_repository_free(repo); + return error; + } + + git_buf_free(&parent); + *repo_ptr = repo; + return 0; +} + +int git_repository_open(git_repository **repo_out, const char *path) +{ + return git_repository_open_ext( + repo_out, path, GIT_REPOSITORY_OPEN_NO_SEARCH, NULL); +} + +int git_repository_discover( + char *repository_path, + size_t size, + const char *start_path, + int across_fs, + const char *ceiling_dirs) +{ + git_buf path = GIT_BUF_INIT; + uint32_t flags = across_fs ? GIT_REPOSITORY_OPEN_CROSS_FS : 0; + + assert(start_path && repository_path && size > 0); + + *repository_path = '\0'; + + if (find_repo(&path, NULL, start_path, flags, ceiling_dirs) < 0) + return -1; + + if (size < (size_t)(path.size + 1)) { + giterr_set(GITERR_REPOSITORY, + "The given buffer is too long to store the discovered path"); + git_buf_free(&path); + return -1; + } + + /* success: we discovered a repository */ + git_buf_copy_cstr(repository_path, size, &path); + git_buf_free(&path); + return 0; } static int load_config( - git_config **out, - git_repository *repo, - const char *global_config_path, - const char *system_config_path) + git_config **out, + git_repository *repo, + const char *global_config_path, + const char *system_config_path) { git_buf config_path = GIT_BUF_INIT; - int error; git_config *cfg = NULL; assert(repo && out); - error = git_config_new(&cfg); - if (error < GIT_SUCCESS) - return error; + if (git_config_new(&cfg) < 0) + return -1; + + if (git_buf_joinpath( + &config_path, repo->path_repository, GIT_CONFIG_FILENAME_INREPO) < 0) + goto on_error; - error = git_buf_joinpath(&config_path, repo->path_repository, - GIT_CONFIG_FILENAME_INREPO); - if (error < GIT_SUCCESS) - goto cleanup; + if (git_config_add_file_ondisk(cfg, config_path.ptr, 3) < 0) + goto on_error; - error = git_config_add_file_ondisk(cfg, config_path.ptr, 3); - git_buf_free(&config_path); /* done with config_path now */ - if (error < GIT_SUCCESS) - goto cleanup; + git_buf_free(&config_path); if (global_config_path != NULL) { - error = git_config_add_file_ondisk(cfg, global_config_path, 2); - if (error < GIT_SUCCESS) - goto cleanup; + if (git_config_add_file_ondisk(cfg, global_config_path, 2) < 0) + goto on_error; } if (system_config_path != NULL) { - error = git_config_add_file_ondisk(cfg, system_config_path, 1); - if (error < GIT_SUCCESS) - goto cleanup; + if (git_config_add_file_ondisk(cfg, system_config_path, 1) < 0) + goto on_error; } *out = cfg; - return GIT_SUCCESS; + return 0; -cleanup: +on_error: + git_buf_free(&config_path); git_config_free(cfg); *out = NULL; - return error; + return -1; } int git_repository_config__weakptr(git_config **out, git_repository *repo) { if (repo->_config == NULL) { - int error; git_buf global_buf = GIT_BUF_INIT, system_buf = GIT_BUF_INIT; + int res; const char *global_config_path = NULL; const char *system_config_path = NULL; - if (git_config_find_global_r(&global_buf) == GIT_SUCCESS) + if (git_config_find_global_r(&global_buf) == 0) global_config_path = global_buf.ptr; - if (git_config_find_system_r(&system_buf) == GIT_SUCCESS) + if (git_config_find_system_r(&system_buf) == 0) system_config_path = system_buf.ptr; - error = load_config(&repo->_config, repo, global_config_path, system_config_path); + res = load_config(&repo->_config, repo, global_config_path, system_config_path); git_buf_free(&global_buf); git_buf_free(&system_buf); - if (error < GIT_SUCCESS) - return error; + if (res < 0) + return -1; GIT_REFCOUNT_OWN(repo->_config, repo); } *out = repo->_config; - return GIT_SUCCESS; + return 0; } int git_repository_config(git_config **out, git_repository *repo) { - int error = git_repository_config__weakptr(out, repo); - - if (error == GIT_SUCCESS) { - GIT_REFCOUNT_INC(*out); - } + if (git_repository_config__weakptr(out, repo) < 0) + return -1; - return error; + GIT_REFCOUNT_INC(*out); + return 0; } void git_repository_set_config(git_repository *repo, git_config *config) @@ -312,34 +515,32 @@ int git_repository_odb__weakptr(git_odb **out, git_repository *repo) assert(repo && out); if (repo->_odb == NULL) { - int error; git_buf odb_path = GIT_BUF_INIT; + int res; - error = git_buf_joinpath(&odb_path, repo->path_repository, GIT_OBJECTS_DIR); - if (error < GIT_SUCCESS) - return error; + if (git_buf_joinpath(&odb_path, repo->path_repository, GIT_OBJECTS_DIR) < 0) + return -1; - error = git_odb_open(&repo->_odb, odb_path.ptr); + res = git_odb_open(&repo->_odb, odb_path.ptr); git_buf_free(&odb_path); /* done with path */ - if (error < GIT_SUCCESS) - return error; + + if (res < 0) + return -1; GIT_REFCOUNT_OWN(repo->_odb, repo); } *out = repo->_odb; - return GIT_SUCCESS; + return 0; } int git_repository_odb(git_odb **out, git_repository *repo) { - int error = git_repository_odb__weakptr(out, repo); + if (git_repository_odb__weakptr(out, repo) < 0) + return -1; - if (error == GIT_SUCCESS) { - GIT_REFCOUNT_INC(*out); - } - - return error; + GIT_REFCOUNT_INC(*out); + return 0; } void git_repository_set_odb(git_repository *repo, git_odb *odb) @@ -350,6 +551,7 @@ void git_repository_set_odb(git_repository *repo, git_odb *odb) repo->_odb = odb; GIT_REFCOUNT_OWN(repo->_odb, repo); + GIT_REFCOUNT_INC(odb); } int git_repository_index__weakptr(git_index **out, git_repository *repo) @@ -357,34 +559,32 @@ int git_repository_index__weakptr(git_index **out, git_repository *repo) assert(out && repo); if (repo->_index == NULL) { - int error; + int res; git_buf index_path = GIT_BUF_INIT; - error = git_buf_joinpath(&index_path, repo->path_repository, GIT_INDEX_FILE); - if (error < GIT_SUCCESS) - return error; + if (git_buf_joinpath(&index_path, repo->path_repository, GIT_INDEX_FILE) < 0) + return -1; - error = git_index_open(&repo->_index, index_path.ptr); + res = git_index_open(&repo->_index, index_path.ptr); git_buf_free(&index_path); /* done with path */ - if (error < GIT_SUCCESS) - return error; + + if (res < 0) + return -1; GIT_REFCOUNT_OWN(repo->_index, repo); } *out = repo->_index; - return GIT_SUCCESS; + return 0; } int git_repository_index(git_index **out, git_repository *repo) { - int error = git_repository_index__weakptr(out, repo); - - if (error == GIT_SUCCESS) { - GIT_REFCOUNT_INC(*out); - } + if (git_repository_index__weakptr(out, repo) < 0) + return -1; - return error; + GIT_REFCOUNT_INC(*out); + return 0; } void git_repository_set_index(git_repository *repo, git_index *index) @@ -395,337 +595,97 @@ void git_repository_set_index(git_repository *repo, git_index *index) repo->_index = index; GIT_REFCOUNT_OWN(repo->_index, repo); -} - - -static int retrieve_device(dev_t *device_out, const char *path) -{ - struct stat path_info; - - assert(device_out); - - if (p_lstat(path, &path_info)) - return git__throw(GIT_EOSERR, "Failed to get file informations: %s", path); - - *device_out = path_info.st_dev; - - return GIT_SUCCESS; -} - -/* - * This function returns furthest offset into path where a ceiling dir - * is found, so we can stop processing the path at that point. - * - * Note: converting this to use git_bufs instead of GIT_PATH_MAX buffers on - * the stack could remove directories name limits, but at the cost of doing - * repeated malloc/frees inside the loop below, so let's not do it now. - */ -static int retrieve_ceiling_directories_offset( - const char *path, - const char *ceiling_directories) -{ - char buf[GIT_PATH_MAX + 1]; - char buf2[GIT_PATH_MAX + 1]; - const char *ceil, *sep; - int len, max_len = -1; - int min_len; - - assert(path); - - min_len = git_path_root(path) + 1; - - if (ceiling_directories == NULL || min_len == 0) - return min_len; - - for (sep = ceil = ceiling_directories; *sep; ceil = sep + 1) { - for (sep = ceil; *sep && *sep != GIT_PATH_LIST_SEPARATOR; sep++); - len = sep - ceil; - - if (len == 0 || len >= (int)sizeof(buf) || git_path_root(ceil) == -1) - continue; - - strncpy(buf, ceil, len); - buf[len] = '\0'; - - if (p_realpath(buf, buf2) == NULL) - continue; - - len = strlen(buf2); - if (len > 0 && buf2[len-1] == '/') - buf[--len] = '\0'; - - if (!strncmp(path, buf2, len) && - path[len] == '/' && - len > max_len) - { - max_len = len; - } - } - - return max_len <= min_len ? min_len : max_len; -} - -/* - * Read the contents of `file_path` and set `path_out` to the repo dir that - * it points to. Before calling, set `path_out` to the base directory that - * should be used if the contents of `file_path` are a relative path. - */ -static int read_gitfile(git_buf *path_out, const char *file_path, const char *base_path) -{ - git_buf file = GIT_BUF_INIT; - int error; - - assert(path_out && file_path); - - error = git_futils_readbuffer(&file, file_path); - if (error < GIT_SUCCESS) - return error; - - if (git__prefixcmp((char *)file.ptr, GIT_FILE_CONTENT_PREFIX)) { - git_buf_free(&file); - return git__throw(GIT_ENOTFOUND, "Invalid gitfile format `%s`", file_path); - } - - git_buf_rtrim(&file); - - if (strlen(GIT_FILE_CONTENT_PREFIX) == file.size) { - git_buf_free(&file); - return git__throw(GIT_ENOTFOUND, "No path in git file `%s`", file_path); - } - - error = git_path_prettify_dir(path_out, - ((char *)file.ptr) + strlen(GIT_FILE_CONTENT_PREFIX), base_path); - - git_buf_free(&file); - - if (error == GIT_SUCCESS && git_path_exists(path_out->ptr) == 0) - return GIT_SUCCESS; - - return git__throw(GIT_EOBJCORRUPTED, "The `.git` file points to a nonexistent path"); -} - -int git_repository_discover( - char *repository_path, - size_t size, - const char *start_path, - int across_fs, - const char *ceiling_dirs) -{ - int error, ceiling_offset; - git_buf bare_path = GIT_BUF_INIT; - git_buf normal_path = GIT_BUF_INIT; - git_buf *found_path = NULL; - dev_t current_device = 0; - - assert(start_path && repository_path); - - *repository_path = '\0'; - - error = git_path_prettify_dir(&bare_path, start_path, NULL); - if (error < GIT_SUCCESS) - goto cleanup; - - if (!across_fs) { - error = retrieve_device(¤t_device, bare_path.ptr); - if (error < GIT_SUCCESS) - goto cleanup; - } - - ceiling_offset = retrieve_ceiling_directories_offset(bare_path.ptr, ceiling_dirs); - - while(1) { - error = git_buf_joinpath(&normal_path, bare_path.ptr, DOT_GIT); - if (error < GIT_SUCCESS) - break; - - /** - * If the `.git` file is regular instead of - * a directory, it should contain the path of the actual git repository - */ - if (git_path_isfile(normal_path.ptr) == GIT_SUCCESS) { - git_buf gitfile_path = GIT_BUF_INIT; - - error = read_gitfile(&gitfile_path, normal_path.ptr, bare_path.ptr); - if (error < GIT_SUCCESS) - git__rethrow(error, "Unable to read git file `%s`", normal_path.ptr); - else if ((error = quickcheck_repository_dir(&gitfile_path)) < GIT_SUCCESS) - git__throw(GIT_ENOTFOUND, - "The `.git` file found at '%s' points " - "to a nonexistent git folder", normal_path.ptr); - else { - git_buf_swap(&normal_path, &gitfile_path); - found_path = &normal_path; - } - - git_buf_free(&gitfile_path); - break; - } - - /** - * If the `.git` file is a folder, we check inside of it - */ - if (git_path_isdir(normal_path.ptr) == GIT_SUCCESS) { - error = quickcheck_repository_dir(&normal_path); - if (error == GIT_SUCCESS) { - found_path = &normal_path; - break; - } - } - - /** - * Otherwise, the repository may be bare, let's check - * the root anyway - */ - error = quickcheck_repository_dir(&bare_path); - if (error == GIT_SUCCESS) { - found_path = &bare_path; - break; - } - - /** - * If we didn't find it, walk up the tree - */ - error = git_path_dirname_r(&normal_path, bare_path.ptr); - if (error < GIT_SUCCESS) { - git__rethrow(GIT_EOSERR, "Failed to dirname '%s'", bare_path.ptr); - break; - } - - git_buf_swap(&bare_path, &normal_path); - - if (!across_fs) { - dev_t new_device; - error = retrieve_device(&new_device, bare_path.ptr); - - if (error < GIT_SUCCESS || current_device != new_device) { - error = git__throw(GIT_ENOTAREPO, - "Not a git repository (or any parent up to mount parent %s)\n" - "Stopping at filesystem boundary.", normal_path.ptr); - break; - } - current_device = new_device; - } - - /* nothing has been found, lets try the parent directory - * but stop if we hit one of the ceiling directories - */ - if (bare_path.ptr[ceiling_offset] == '\0') { - error = git__throw(GIT_ENOTAREPO, - "Not a git repository (or any of the parent directories): %s", start_path); - break; - } - } - - assert(found_path || error != GIT_SUCCESS); - - if (found_path) { - if ((error = git_path_to_dir(found_path)) < GIT_SUCCESS) - git__rethrow(error, "Could not convert git repository to directory"); - else if (size < (size_t)(found_path->size + 1)) - error = git__throw(GIT_ESHORTBUFFER, - "The repository buffer is not long enough to " - "handle the repository path `%s`", found_path->ptr); - else - git_buf_copy_cstr(repository_path, size, found_path); - } - -cleanup: - git_buf_free(&bare_path); - git_buf_free(&normal_path); - return error; + GIT_REFCOUNT_INC(index); } static int check_repositoryformatversion(git_repository *repo) { git_config *config; - int version, error = GIT_SUCCESS; - - if ((error = git_repository_config(&config, repo)) < GIT_SUCCESS) - return git__throw(error, "Failed to open config file."); + int version; - error = git_config_get_int32(config, GIT_CONFIG_CORE_REPOSITORYFORMATVERSION, &version); + if (git_repository_config__weakptr(&config, repo) < 0) + return -1; - if (GIT_REPOSITORYFORMATVERSION < version) - error = git__throw(GIT_ERROR, "Unsupported git repository version (Expected version <= %d, found %d).", GIT_REPOSITORYFORMATVERSION, version); + if (git_config_get_int32(config, GIT_CONFIG_CORE_REPOSITORYFORMATVERSION, &version) < 0) + return -1; - git_config_free(config); + if (GIT_REPOSITORYFORMATVERSION < version) { + giterr_set(GITERR_REPOSITORY, + "Unsupported repository version %d. Only versions up to %d are supported.", + version, GIT_REPOSITORYFORMATVERSION); + return -1; + } - return error; + return 0; } static int repo_init_reinit(git_repository **repo_out, const char *repository_path, int is_bare) { - int error; git_repository *repo = NULL; - if ((error = git_repository_open(&repo, repository_path)) < GIT_SUCCESS) - goto error; + GIT_UNUSED(is_bare); + + if (git_repository_open(&repo, repository_path) < 0) + return -1; - if ((error = check_repositoryformatversion(repo)) < GIT_SUCCESS) - goto error; + if (check_repositoryformatversion(repo) < 0) { + git_repository_free(repo); + return -1; + } /* TODO: reinitialize the templates */ *repo_out = repo; - - return GIT_SUCCESS; - -error: - git_repository_free(repo); - - return git__rethrow(error, - "Failed to reinitialize the %srepository at '%s'. ", - is_bare ? "bare " : "", repository_path); + return 0; } static int repo_init_createhead(const char *git_dir) { - int error; git_buf ref_path = GIT_BUF_INIT; git_filebuf ref = GIT_FILEBUF_INIT; - if (!(error = git_buf_joinpath(&ref_path, git_dir, GIT_HEAD_FILE)) && - !(error = git_filebuf_open(&ref, ref_path.ptr, 0)) && - !(error = git_filebuf_printf(&ref, "ref: refs/heads/master\n"))) - error = git_filebuf_commit(&ref, GIT_REFS_FILE_MODE); + if (git_buf_joinpath(&ref_path, git_dir, GIT_HEAD_FILE) < 0 || + git_filebuf_open(&ref, ref_path.ptr, 0) < 0 || + git_filebuf_printf(&ref, "ref: refs/heads/master\n") < 0 || + git_filebuf_commit(&ref, GIT_REFS_FILE_MODE) < 0) + return -1; git_buf_free(&ref_path); - return error; + return 0; } static int repo_init_config(const char *git_dir, int is_bare) { git_buf cfg_path = GIT_BUF_INIT; git_config *config = NULL; - int error = GIT_SUCCESS; #define SET_REPO_CONFIG(type, name, val) {\ - error = git_config_set_##type(config, name, val);\ - if (error < GIT_SUCCESS)\ - goto cleanup;\ + if (git_config_set_##type(config, name, val) < 0) { \ + git_buf_free(&cfg_path); \ + git_config_free(config); \ + return -1; } \ } - error = git_buf_joinpath(&cfg_path, git_dir, GIT_CONFIG_FILENAME_INREPO); - if (error < GIT_SUCCESS) - goto cleanup; + if (git_buf_joinpath(&cfg_path, git_dir, GIT_CONFIG_FILENAME_INREPO) < 0) + return -1; - error = git_config_open_ondisk(&config, cfg_path.ptr); - if (error < GIT_SUCCESS) - goto cleanup; + if (git_config_open_ondisk(&config, cfg_path.ptr) < 0) { + git_buf_free(&cfg_path); + return -1; + } SET_REPO_CONFIG(bool, "core.bare", is_bare); SET_REPO_CONFIG(int32, GIT_CONFIG_CORE_REPOSITORYFORMATVERSION, GIT_REPOSITORYFORMATVERSION); /* TODO: what other defaults? */ -cleanup: git_buf_free(&cfg_path); git_config_free(config); - return error; + return 0; } static int repo_init_structure(const char *git_dir, int is_bare) { - int error, i; + int i; struct { const char *dir; mode_t mode; } dirs[] = { { GIT_OBJECTS_INFO_DIR, GIT_OBJECT_DIR_MODE }, /* '/objects/info/' */ { GIT_OBJECTS_PACK_DIR, GIT_OBJECT_DIR_MODE }, /* '/objects/pack/' */ @@ -735,136 +695,102 @@ static int repo_init_structure(const char *git_dir, int is_bare) }; /* Make the base directory */ - error = git_futils_mkdir_r(git_dir, NULL, is_bare ? - GIT_BARE_DIR_MODE : GIT_DIR_MODE); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to initialize repository structure. Could not mkdir"); + if (git_futils_mkdir_r(git_dir, NULL, is_bare ? GIT_BARE_DIR_MODE : GIT_DIR_MODE) < 0) + return -1; /* Hides the ".git" directory */ if (!is_bare) { #ifdef GIT_WIN32 - error = p_hide_directory__w32(git_dir); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to initialize repository structure"); + if (p_hide_directory__w32(git_dir) < 0) { + giterr_set(GITERR_REPOSITORY, + "Failed to mark Git repository folder as hidden"); + return -1; + } #endif } /* Make subdirectories as needed */ for (i = 0; dirs[i].dir != NULL; ++i) { - error = git_futils_mkdir_r(dirs[i].dir, git_dir, dirs[i].mode); - if (error < GIT_SUCCESS) - return git__rethrow(error, - "Failed to create repository folder `%s`", dirs[i].dir); + if (git_futils_mkdir_r(dirs[i].dir, git_dir, dirs[i].mode) < 0) + return -1; } /* TODO: what's left? templates? */ - - return error; + return 0; } int git_repository_init(git_repository **repo_out, const char *path, unsigned is_bare) { - int error = GIT_SUCCESS; - git_repository *repo = NULL; git_buf repository_path = GIT_BUF_INIT; assert(repo_out && path); - error = git_buf_joinpath(&repository_path, path, is_bare ? "" : GIT_DIR); - if (error < GIT_SUCCESS) - return error; + if (git_buf_joinpath(&repository_path, path, is_bare ? "" : GIT_DIR) < 0) + return -1; - if (git_path_isdir(repository_path.ptr) == GIT_SUCCESS) { - if (quickcheck_repository_dir(&repository_path) == GIT_SUCCESS) { - error = repo_init_reinit(repo_out, repository_path.ptr, is_bare); + if (git_path_isdir(repository_path.ptr) == true) { + if (valid_repository_path(&repository_path) == true) { + int res = repo_init_reinit(repo_out, repository_path.ptr, is_bare); git_buf_free(&repository_path); - return error; + return res; } } - if (!(error = repo_init_structure(repository_path.ptr, is_bare)) && - !(error = repo_init_config(repository_path.ptr, is_bare)) && - !(error = repo_init_createhead(repository_path.ptr))) - error = git_repository_open(repo_out, repository_path.ptr); - else - git_repository_free(repo); + if (repo_init_structure(repository_path.ptr, is_bare) < 0 || + repo_init_config(repository_path.ptr, is_bare) < 0 || + repo_init_createhead(repository_path.ptr) < 0 || + git_repository_open(repo_out, repository_path.ptr) < 0) { + git_buf_free(&repository_path); + return -1; + } git_buf_free(&repository_path); - - if (error != GIT_SUCCESS) - git__rethrow(error, "Failed to (re)init the repository `%s`", path); - - return error; + return 0; } int git_repository_head_detached(git_repository *repo) { git_reference *ref; - int error; - size_t _size; - git_otype type; git_odb *odb = NULL; + int exists; - error = git_repository_odb__weakptr(&odb, repo); - if (error < GIT_SUCCESS) - return error; + if (git_repository_odb__weakptr(&odb, repo) < 0) + return -1; - error = git_reference_lookup(&ref, repo, GIT_HEAD_FILE); - if (error < GIT_SUCCESS) - return error; + if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0) + return -1; if (git_reference_type(ref) == GIT_REF_SYMBOLIC) { git_reference_free(ref); return 0; } - error = git_odb_read_header(&_size, &type, odb, git_reference_oid(ref)); + exists = git_odb_exists(odb, git_reference_oid(ref)); git_reference_free(ref); - - if (error < GIT_SUCCESS) - return error; - - if (type != GIT_OBJ_COMMIT) - return git__throw(GIT_EOBJCORRUPTED, "HEAD is not a commit"); - - return 1; + return exists; } int git_repository_head(git_reference **head_out, git_repository *repo) { - git_reference *ref, *resolved_ref; - int error; - - *head_out = NULL; - - error = git_reference_lookup(&ref, repo, GIT_HEAD_FILE); - if (error < GIT_SUCCESS) - return git__rethrow(GIT_ENOTAREPO, "Failed to locate the HEAD"); - - error = git_reference_resolve(&resolved_ref, ref); - if (error < GIT_SUCCESS) { - git_reference_free(ref); - return git__rethrow(error, "Failed to resolve the HEAD"); - } - - git_reference_free(ref); - - *head_out = resolved_ref; - return GIT_SUCCESS; + return git_reference_lookup_resolved(head_out, repo, GIT_HEAD_FILE, -1); } int git_repository_head_orphan(git_repository *repo) { - git_reference *ref; + git_reference *ref = NULL; int error; error = git_repository_head(&ref, repo); + git_reference_free(ref); - if (error == GIT_SUCCESS) - git_reference_free(ref); + if (error == GIT_ENOTFOUND) + return 1; - return error == GIT_ENOTFOUND ? 1 : error; + if (error < 0) + return -1; + + return 0; } int git_repository_is_empty(git_repository *repo) @@ -872,9 +798,8 @@ int git_repository_is_empty(git_repository *repo) git_reference *head = NULL, *branch = NULL; int error; - error = git_reference_lookup(&head, repo, "HEAD"); - if (error < GIT_SUCCESS) - return git__throw(error, "Corrupted repository. HEAD does not exist"); + if (git_reference_lookup(&head, repo, "HEAD") < 0) + return -1; if (git_reference_type(head) != GIT_REF_SYMBOLIC) { git_reference_free(head); @@ -891,7 +816,13 @@ int git_repository_is_empty(git_repository *repo) git_reference_free(head); git_reference_free(branch); - return error == GIT_ENOTFOUND ? 1 : error; + if (error == GIT_ENOTFOUND) + return 1; + + if (error < 0) + return -1; + + return 0; } const char *git_repository_path(git_repository *repo) @@ -912,16 +843,18 @@ const char *git_repository_workdir(git_repository *repo) int git_repository_set_workdir(git_repository *repo, const char *workdir) { + git_buf path = GIT_BUF_INIT; + assert(repo && workdir); - free(repo->workdir); + if (git_path_prettify_dir(&path, workdir, NULL) < 0) + return -1; - repo->workdir = git__strdup(workdir); - if (repo->workdir == NULL) - return GIT_ENOMEM; + git__free(repo->workdir); + repo->workdir = git_buf_detach(&path); repo->is_bare = 0; - return GIT_SUCCESS; + return 0; } int git_repository_is_bare(git_repository *repo) diff --git a/src/repository.h b/src/repository.h index b5dcc1340..1ffac58f1 100644 --- a/src/repository.h +++ b/src/repository.h @@ -13,13 +13,13 @@ #include "git2/repository.h" #include "git2/object.h" -#include "hashtable.h" #include "index.h" #include "cache.h" #include "refs.h" #include "buffer.h" #include "odb.h" #include "attr.h" +#include "strmap.h" #define DOT_GIT ".git" #define GIT_DIR DOT_GIT "/" @@ -83,6 +83,7 @@ struct git_repository { git_cache objects; git_refcache references; git_attr_cache attrcache; + git_strmap *submodules; char *path_repository; char *workdir; @@ -100,6 +101,11 @@ void git_object__free(void *object); int git_oid__parse(git_oid *oid, const char **buffer_out, const char *buffer_end, const char *header); void git_oid__writebuf(git_buf *buf, const char *header, const git_oid *oid); +GIT_INLINE(git_attr_cache *) git_repository_attr_cache(git_repository *repo) +{ + return &repo->attrcache; +} + /* * Weak pointers to repository internals. * @@ -120,4 +126,9 @@ int git_repository_index__weakptr(git_index **out, git_repository *repo); int git_repository__cvar(int *out, git_repository *repo, git_cvar_cached cvar); void git_repository__cvar_cache_clear(git_repository *repo); +/* + * Submodule cache + */ +extern void git_submodule_config_free(git_repository *repo); + #endif diff --git a/src/revwalk.c b/src/revwalk.c index 997771f08..c62bb4e0e 100644 --- a/src/revwalk.c +++ b/src/revwalk.c @@ -8,20 +8,30 @@ #include "common.h" #include "commit.h" #include "odb.h" -#include "hashtable.h" #include "pqueue.h" +#include "pool.h" +#include "oidmap.h" #include "git2/revwalk.h" +#include "git2/merge.h" #include <regex.h> +GIT__USE_OIDMAP; + +#define PARENT1 (1 << 0) +#define PARENT2 (1 << 1) +#define RESULT (1 << 2) +#define STALE (1 << 3) + typedef struct commit_object { git_oid oid; uint32_t time; unsigned int seen:1, uninteresting:1, topo_delay:1, - parsed:1; + parsed:1, + flags : 4; unsigned short in_degree; unsigned short out_degree; @@ -38,7 +48,8 @@ struct git_revwalk { git_repository *repo; git_odb *odb; - git_hashtable *commits; + git_oidmap *commits; + git_pool commit_pool; commit_list *iterator_topo; commit_list *iterator_rand; @@ -48,22 +59,47 @@ struct git_revwalk { int (*get_next)(commit_object **, git_revwalk *); int (*enqueue)(git_revwalk *, commit_object *); - git_vector memory_alloc; - size_t chunk_size; - unsigned walking:1; unsigned int sorting; + + /* merge base calculation */ + commit_object *one; + git_vector twos; }; +static int commit_time_cmp(void *a, void *b) +{ + commit_object *commit_a = (commit_object *)a; + commit_object *commit_b = (commit_object *)b; + + return (commit_a->time < commit_b->time); +} + static commit_list *commit_list_insert(commit_object *item, commit_list **list_p) { commit_list *new_list = git__malloc(sizeof(commit_list)); - new_list->item = item; - new_list->next = *list_p; + if (new_list != NULL) { + new_list->item = item; + new_list->next = *list_p; + } *list_p = new_list; return new_list; } +static commit_list *commit_list_insert_by_date(commit_object *item, commit_list **list_p) +{ + commit_list **pp = list_p; + commit_list *p; + + while ((p = *pp) != NULL) { + if (commit_time_cmp(p->item, item) < 0) + break; + + pp = &p->next; + } + + return commit_list_insert(item, pp); +} static void commit_list_free(commit_list **list_p) { commit_list *list = *list_p; @@ -89,68 +125,36 @@ static commit_object *commit_list_pop(commit_list **stack) return item; } -static int commit_time_cmp(void *a, void *b) -{ - commit_object *commit_a = (commit_object *)a; - commit_object *commit_b = (commit_object *)b; - - return (commit_a->time < commit_b->time); -} - -static uint32_t object_table_hash(const void *key, int hash_id) -{ - uint32_t r; - const git_oid *id = key; - - memcpy(&r, id->id + (hash_id * sizeof(uint32_t)), sizeof(r)); - return r; -} - -#define COMMITS_PER_CHUNK 128 -#define CHUNK_STEP 64 -#define PARENTS_PER_COMMIT ((CHUNK_STEP - sizeof(commit_object)) / sizeof(commit_object *)) - -static int alloc_chunk(git_revwalk *walk) -{ - void *chunk; - - chunk = git__calloc(COMMITS_PER_CHUNK, CHUNK_STEP); - if (chunk == NULL) - return GIT_ENOMEM; - - walk->chunk_size = 0; - return git_vector_insert(&walk->memory_alloc, chunk); -} +#define PARENTS_PER_COMMIT 2 +#define COMMIT_ALLOC \ + (sizeof(commit_object) + PARENTS_PER_COMMIT * sizeof(commit_object *)) static commit_object *alloc_commit(git_revwalk *walk) { - unsigned char *chunk; - - if (walk->chunk_size == COMMITS_PER_CHUNK) - alloc_chunk(walk); - - chunk = git_vector_get(&walk->memory_alloc, walk->memory_alloc.length - 1); - chunk += (walk->chunk_size * CHUNK_STEP); - walk->chunk_size++; - - return (commit_object *)chunk; + return (commit_object *)git_pool_malloc(&walk->commit_pool, COMMIT_ALLOC); } -static commit_object **alloc_parents(commit_object *commit, size_t n_parents) +static commit_object **alloc_parents( + git_revwalk *walk, commit_object *commit, size_t n_parents) { if (n_parents <= PARENTS_PER_COMMIT) - return (commit_object **)((unsigned char *)commit + sizeof(commit_object)); + return (commit_object **)((char *)commit + sizeof(commit_object)); - return git__malloc(n_parents * sizeof(commit_object *)); + return (commit_object **)git_pool_malloc( + &walk->commit_pool, (uint32_t)(n_parents * sizeof(commit_object *))); } static commit_object *commit_lookup(git_revwalk *walk, const git_oid *oid) { commit_object *commit; + khiter_t pos; + int ret; - if ((commit = git_hashtable_lookup(walk->commits, oid)) != NULL) - return commit; + /* lookup and reserve space if not already present */ + pos = kh_get(oid, walk->commits, oid); + if (pos != kh_end(walk->commits)) + return kh_value(walk->commits, pos); commit = alloc_commit(walk); if (commit == NULL) @@ -158,17 +162,16 @@ static commit_object *commit_lookup(git_revwalk *walk, const git_oid *oid) git_oid_cpy(&commit->oid, oid); - if (git_hashtable_insert(walk->commits, &commit->oid, commit) < GIT_SUCCESS) { - git__free(commit); - return NULL; - } + pos = kh_put(oid, walk->commits, &commit->oid, &ret); + assert(ret != 0); + kh_value(walk->commits, pos) = commit; return commit; } static int commit_quick_parse(git_revwalk *walk, commit_object *commit, git_rawobj *raw) { - const int parent_len = strlen("parent ") + GIT_OID_HEXSZ + 1; + const size_t parent_len = strlen("parent ") + GIT_OID_HEXSZ + 1; unsigned char *buffer = raw->data; unsigned char *buffer_end = buffer + raw->len; @@ -185,39 +188,44 @@ static int commit_quick_parse(git_revwalk *walk, commit_object *commit, git_rawo buffer += parent_len; } - commit->parents = alloc_parents(commit, parents); - if (commit->parents == NULL) - return GIT_ENOMEM; + commit->parents = alloc_parents(walk, commit, parents); + GITERR_CHECK_ALLOC(commit->parents); buffer = parents_start; for (i = 0; i < parents; ++i) { git_oid oid; - if (git_oid_fromstr(&oid, (char *)buffer + strlen("parent ")) < GIT_SUCCESS) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse commit. Parent object is corrupted"); + if (git_oid_fromstr(&oid, (char *)buffer + strlen("parent ")) < 0) + return -1; commit->parents[i] = commit_lookup(walk, &oid); if (commit->parents[i] == NULL) - return GIT_ENOMEM; + return -1; buffer += parent_len; } commit->out_degree = (unsigned short)parents; - if ((buffer = memchr(buffer, '\n', buffer_end - buffer)) == NULL) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse commit. Object is corrupted"); + if ((buffer = memchr(buffer, '\n', buffer_end - buffer)) == NULL) { + giterr_set(GITERR_ODB, "Failed to parse commit. Object is corrupted"); + return -1; + } buffer = memchr(buffer, '>', buffer_end - buffer); - if (buffer == NULL) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse commit. Can't find author"); + if (buffer == NULL) { + giterr_set(GITERR_ODB, "Failed to parse commit. Can't find author"); + return -1; + } - if (git__strtol32(&commit_time, (char *)buffer + 2, NULL, 10) < GIT_SUCCESS) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse commit. Can't parse commit time"); + if (git__strtol32(&commit_time, (char *)buffer + 2, NULL, 10) < 0) { + giterr_set(GITERR_ODB, "Failed to parse commit. Can't parse commit time"); + return -1; + } commit->time = (time_t)commit_time; commit->parsed = 1; - return GIT_SUCCESS; + return 0; } static int commit_parse(git_revwalk *walk, commit_object *commit) @@ -226,19 +234,159 @@ static int commit_parse(git_revwalk *walk, commit_object *commit) int error; if (commit->parsed) - return GIT_SUCCESS; + return 0; - if ((error = git_odb_read(&obj, walk->odb, &commit->oid)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to parse commit. Can't read object"); + if ((error = git_odb_read(&obj, walk->odb, &commit->oid)) < 0) + return error; if (obj->raw.type != GIT_OBJ_COMMIT) { git_odb_object_free(obj); - return git__throw(GIT_EOBJTYPE, "Failed to parse commit. Object is no commit object"); + giterr_set(GITERR_INVALID, "Failed to parse commit. Object is no commit object"); + return -1; } error = commit_quick_parse(walk, commit, &obj->raw); git_odb_object_free(obj); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse commit"); + return error; +} + +static int interesting(git_pqueue *list) +{ + unsigned int i; + for (i = 1; i < git_pqueue_size(list); i++) { + commit_object *commit = list->d[i]; + if ((commit->flags & STALE) == 0) + return 1; + } + + return 0; +} + +static int merge_bases_many(commit_list **out, git_revwalk *walk, commit_object *one, git_vector *twos) +{ + int error; + unsigned int i; + commit_object *two; + commit_list *result = NULL, *tmp = NULL; + git_pqueue list; + + /* if the commit is repeated, we have a our merge base already */ + git_vector_foreach(twos, i, two) { + if (one == two) + return commit_list_insert(one, out) ? 0 : -1; + } + + if (git_pqueue_init(&list, twos->length * 2, commit_time_cmp) < 0) + return -1; + + if (commit_parse(walk, one) < 0) + return -1; + + one->flags |= PARENT1; + if (git_pqueue_insert(&list, one) < 0) + return -1; + + git_vector_foreach(twos, i, two) { + commit_parse(walk, two); + two->flags |= PARENT2; + if (git_pqueue_insert(&list, two) < 0) + return -1; + } + + /* as long as there are non-STALE commits */ + while (interesting(&list)) { + commit_object *commit; + int flags; + + commit = git_pqueue_pop(&list); + + flags = commit->flags & (PARENT1 | PARENT2 | STALE); + if (flags == (PARENT1 | PARENT2)) { + if (!(commit->flags & RESULT)) { + commit->flags |= RESULT; + if (commit_list_insert(commit, &result) == NULL) + return -1; + } + /* we mark the parents of a merge stale */ + flags |= STALE; + } + + for (i = 0; i < commit->out_degree; i++) { + commit_object *p = commit->parents[i]; + if ((p->flags & flags) == flags) + continue; + + if ((error = commit_parse(walk, p)) < GIT_SUCCESS) + return error; + + p->flags |= flags; + if (git_pqueue_insert(&list, p) < 0) + return -1; + } + } + + git_pqueue_free(&list); + + /* filter out any stale commits in the results */ + tmp = result; + result = NULL; + + while (tmp) { + struct commit_list *next = tmp->next; + if (!(tmp->item->flags & STALE)) + if (commit_list_insert_by_date(tmp->item, &result) == NULL) + return -1; + + git__free(tmp); + tmp = next; + } + + *out = result; + return 0; +} + +int git_merge_base(git_oid *out, git_repository *repo, git_oid *one, git_oid *two) +{ + git_revwalk *walk; + git_vector list; + commit_list *result = NULL; + commit_object *commit; + void *contents[1]; + + if (git_revwalk_new(&walk, repo) < 0) + return -1; + + commit = commit_lookup(walk, two); + if (commit == NULL) + goto on_error; + + /* This is just one value, so we can do it on the stack */ + memset(&list, 0x0, sizeof(git_vector)); + contents[0] = commit; + list.length = 1; + list.contents = contents; + + commit = commit_lookup(walk, one); + if (commit == NULL) + goto on_error; + + if (merge_bases_many(&result, walk, commit, &list) < 0) + goto on_error; + + if (!result) { + git_revwalk_free(walk); + return GIT_ENOTFOUND; + } + + git_oid_cpy(out, &result->item->oid); + commit_list_free(&result); + git_revwalk_free(walk); + + return 0; + +on_error: + git_revwalk_free(walk); + return -1; } static void mark_uninteresting(commit_object *commit) @@ -248,6 +396,10 @@ static void mark_uninteresting(commit_object *commit) commit->uninteresting = 1; + /* This means we've reached a merge base, so there's no need to walk any more */ + if ((commit->flags & (RESULT | STALE)) == RESULT) + return; + for (i = 0; i < commit->out_degree; ++i) if (!commit->parents[i]->uninteresting) mark_uninteresting(commit->parents[i]); @@ -261,12 +413,12 @@ static int process_commit(git_revwalk *walk, commit_object *commit, int hide) mark_uninteresting(commit); if (commit->seen) - return GIT_SUCCESS; + return 0; commit->seen = 1; - if ((error = commit_parse(walk, commit)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to process commit"); + if ((error = commit_parse(walk, commit)) < 0) + return error; return walk->enqueue(walk, commit); } @@ -274,13 +426,12 @@ static int process_commit(git_revwalk *walk, commit_object *commit, int hide) static int process_commit_parents(git_revwalk *walk, commit_object *commit) { unsigned short i; - int error = GIT_SUCCESS; + int error = 0; - for (i = 0; i < commit->out_degree && error == GIT_SUCCESS; ++i) { + for (i = 0; i < commit->out_degree && !error; ++i) error = process_commit(walk, commit->parents[i], commit->uninteresting); - } - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to process commit parents"); + return error; } static int push_commit(git_revwalk *walk, const git_oid *oid, int uninteresting) @@ -289,9 +440,17 @@ static int push_commit(git_revwalk *walk, const git_oid *oid, int uninteresting) commit = commit_lookup(walk, oid); if (commit == NULL) - return git__throw(GIT_ENOTFOUND, "Failed to push commit. Object not found"); + return -1; /* error already reported by failed lookup */ - return process_commit(walk, commit, uninteresting); + commit->uninteresting = uninteresting; + if (walk->one == NULL && !uninteresting) { + walk->one = commit; + } else { + if (git_vector_insert(&walk->twos, commit) < 0) + return -1; + } + + return 0; } int git_revwalk_push(git_revwalk *walk, const git_oid *oid) @@ -307,6 +466,16 @@ int git_revwalk_hide(git_revwalk *walk, const git_oid *oid) return push_commit(walk, oid, 1); } +static int push_ref(git_revwalk *walk, const char *refname, int hide) +{ + git_oid oid; + + if (git_reference_name_to_oid(&oid, walk->repo, refname) < 0) + return -1; + + return push_commit(walk, &oid, hide); +} + struct push_cb_data { git_revwalk *walk; const char *glob; @@ -317,30 +486,16 @@ static int push_glob_cb(const char *refname, void *data_) { struct push_cb_data *data = (struct push_cb_data *)data_; - if (!git__fnmatch(data->glob, refname, 0)) { - git_reference *ref, *resolved; - int error; - - error = git_reference_lookup(&ref, data->walk->repo, refname); - if (error < GIT_SUCCESS) - return error; - error = git_reference_resolve(&resolved, ref); - git_reference_free(ref); - if (error < GIT_SUCCESS) - return error; - error = push_commit(data->walk, git_reference_oid(resolved), data->hide); - git_reference_free(resolved); - return error; - } + if (!git__fnmatch(data->glob, refname, 0)) + return push_ref(data->walk, refname, data->hide); - return GIT_SUCCESS; + return 0; } static int push_glob(git_revwalk *walk, const char *glob, int hide) { git_buf buf = GIT_BUF_INIT; struct push_cb_data data; - int error; regex_t preg; assert(walk && glob); @@ -355,28 +510,33 @@ static int push_glob(git_revwalk *walk, const char *glob, int hide) /* If no '?', '*' or '[' exist, we append '/ *' to the glob */ memset(&preg, 0x0, sizeof(regex_t)); if (regcomp(&preg, "[?*[]", REG_EXTENDED)) { - error = git__throw(GIT_EOSERR, "Regex failed to compile"); - goto cleanup; + giterr_set(GITERR_OS, "Regex failed to compile"); + git_buf_free(&buf); + return -1; } if (regexec(&preg, glob, 0, NULL, 0)) git_buf_puts(&buf, "/*"); - if (git_buf_oom(&buf)) { - error = GIT_ENOMEM; - goto cleanup; - } + if (git_buf_oom(&buf)) + goto on_error; data.walk = walk; data.glob = git_buf_cstr(&buf); data.hide = hide; - error = git_reference_foreach(walk->repo, GIT_REF_LISTALL, push_glob_cb, &data); + if (git_reference_foreach( + walk->repo, GIT_REF_LISTALL, push_glob_cb, &data) < 0) + goto on_error; -cleanup: regfree(&preg); git_buf_free(&buf); - return error; + return 0; + +on_error: + regfree(&preg); + git_buf_free(&buf); + return -1; } int git_revwalk_push_glob(git_revwalk *walk, const char *glob) @@ -391,37 +551,28 @@ int git_revwalk_hide_glob(git_revwalk *walk, const char *glob) return push_glob(walk, glob, 1); } -static int push_head(git_revwalk *walk, int hide) -{ - git_reference *ref, *resolved; - int error; - - error = git_reference_lookup(&ref, walk->repo, "HEAD"); - if (error < GIT_SUCCESS) { - return error; - } - error = git_reference_resolve(&resolved, ref); - if (error < GIT_SUCCESS) { - return error; - } - git_reference_free(ref); - - error = push_commit(walk, git_reference_oid(resolved), hide); - - git_reference_free(resolved); - return error; -} - int git_revwalk_push_head(git_revwalk *walk) { assert(walk); - return push_head(walk, 0); + return push_ref(walk, GIT_HEAD_FILE, 0); } int git_revwalk_hide_head(git_revwalk *walk) { assert(walk); - return push_head(walk, 1); + return push_ref(walk, GIT_HEAD_FILE, 1); +} + +int git_revwalk_push_ref(git_revwalk *walk, const char *refname) +{ + assert(walk && refname); + return push_ref(walk, refname, 0); +} + +int git_revwalk_hide_ref(git_revwalk *walk, const char *refname) +{ + assert(walk && refname); + return push_ref(walk, refname, 1); } static int revwalk_enqueue_timesort(git_revwalk *walk, commit_object *commit) @@ -431,7 +582,7 @@ static int revwalk_enqueue_timesort(git_revwalk *walk, commit_object *commit) static int revwalk_enqueue_unsorted(git_revwalk *walk, commit_object *commit) { - return commit_list_insert(commit, &walk->iterator_rand) ? GIT_SUCCESS : GIT_ENOMEM; + return commit_list_insert(commit, &walk->iterator_rand) ? 0 : -1; } static int revwalk_next_timesort(commit_object **object_out, git_revwalk *walk) @@ -440,16 +591,16 @@ static int revwalk_next_timesort(commit_object **object_out, git_revwalk *walk) commit_object *next; while ((next = git_pqueue_pop(&walk->iterator_time)) != NULL) { - if ((error = process_commit_parents(walk, next)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to load next revision"); + if ((error = process_commit_parents(walk, next)) < 0) + return error; if (!next->uninteresting) { *object_out = next; - return GIT_SUCCESS; + return 0; } } - return git__throw(GIT_EREVWALKOVER, "Failed to load next revision"); + return GIT_EREVWALKOVER; } static int revwalk_next_unsorted(commit_object **object_out, git_revwalk *walk) @@ -458,16 +609,16 @@ static int revwalk_next_unsorted(commit_object **object_out, git_revwalk *walk) commit_object *next; while ((next = commit_list_pop(&walk->iterator_rand)) != NULL) { - if ((error = process_commit_parents(walk, next)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to load next revision"); + if ((error = process_commit_parents(walk, next)) < 0) + return error; if (!next->uninteresting) { *object_out = next; - return GIT_SUCCESS; + return 0; } } - return git__throw(GIT_EREVWALKOVER, "Failed to load next revision"); + return GIT_EREVWALKOVER; } static int revwalk_next_toposort(commit_object **object_out, git_revwalk *walk) @@ -478,7 +629,7 @@ static int revwalk_next_toposort(commit_object **object_out, git_revwalk *walk) for (;;) { next = commit_list_pop(&walk->iterator_topo); if (next == NULL) - return git__throw(GIT_EREVWALKOVER, "Failed to load next revision"); + return GIT_EREVWALKOVER; if (next->in_degree > 0) { next->topo_delay = 1; @@ -490,58 +641,83 @@ static int revwalk_next_toposort(commit_object **object_out, git_revwalk *walk) if (--parent->in_degree == 0 && parent->topo_delay) { parent->topo_delay = 0; - commit_list_insert(parent, &walk->iterator_topo); + if (commit_list_insert(parent, &walk->iterator_topo) == NULL) + return -1; } } *object_out = next; - return GIT_SUCCESS; + return 0; } } static int revwalk_next_reverse(commit_object **object_out, git_revwalk *walk) { *object_out = commit_list_pop(&walk->iterator_reverse); - return *object_out ? GIT_SUCCESS : GIT_EREVWALKOVER; + return *object_out ? 0 : GIT_EREVWALKOVER; } static int prepare_walk(git_revwalk *walk) { int error; - commit_object *next; + unsigned int i; + commit_object *next, *two; + commit_list *bases = NULL; + + /* + * If walk->one is NULL, there were no positive references, + * so we know that the walk is already over. + */ + if (walk->one == NULL) + return GIT_EREVWALKOVER; + + /* first figure out what the merge bases are */ + if (merge_bases_many(&bases, walk, walk->one, &walk->twos) < 0) + return -1; + + commit_list_free(&bases); + if (process_commit(walk, walk->one, walk->one->uninteresting) < 0) + return -1; + + git_vector_foreach(&walk->twos, i, two) { + if (process_commit(walk, two, two->uninteresting) < 0) + return -1; + } if (walk->sorting & GIT_SORT_TOPOLOGICAL) { unsigned short i; - while ((error = walk->get_next(&next, walk)) == GIT_SUCCESS) { + while ((error = walk->get_next(&next, walk)) == 0) { for (i = 0; i < next->out_degree; ++i) { commit_object *parent = next->parents[i]; parent->in_degree++; } - commit_list_insert(next, &walk->iterator_topo); + if (commit_list_insert(next, &walk->iterator_topo) == NULL) + return -1; } if (error != GIT_EREVWALKOVER) - return git__rethrow(error, "Failed to prepare revision walk"); + return error; walk->get_next = &revwalk_next_toposort; } if (walk->sorting & GIT_SORT_REVERSE) { - while ((error = walk->get_next(&next, walk)) == GIT_SUCCESS) - commit_list_insert(next, &walk->iterator_reverse); + while ((error = walk->get_next(&next, walk)) == 0) + if (commit_list_insert(next, &walk->iterator_reverse) == NULL) + return -1; if (error != GIT_EREVWALKOVER) - return git__rethrow(error, "Failed to prepare revision walk"); + return error; walk->get_next = &revwalk_next_reverse; } walk->walking = 1; - return GIT_SUCCESS; + return 0; } @@ -550,69 +726,48 @@ static int prepare_walk(git_revwalk *walk) int git_revwalk_new(git_revwalk **revwalk_out, git_repository *repo) { - int error; git_revwalk *walk; walk = git__malloc(sizeof(git_revwalk)); - if (walk == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(walk); memset(walk, 0x0, sizeof(git_revwalk)); - walk->commits = git_hashtable_alloc(64, - object_table_hash, - (git_hash_keyeq_ptr)git_oid_cmp); - - if (walk->commits == NULL) { - git__free(walk); - return GIT_ENOMEM; - } + walk->commits = git_oidmap_alloc(); + GITERR_CHECK_ALLOC(walk->commits); - git_pqueue_init(&walk->iterator_time, 8, commit_time_cmp); - git_vector_init(&walk->memory_alloc, 8, NULL); - alloc_chunk(walk); + if (git_pqueue_init(&walk->iterator_time, 8, commit_time_cmp) < 0 || + git_vector_init(&walk->twos, 4, NULL) < 0 || + git_pool_init(&walk->commit_pool, 1, + git_pool__suggest_items_per_page(COMMIT_ALLOC) * COMMIT_ALLOC) < 0) + return -1; walk->get_next = &revwalk_next_unsorted; walk->enqueue = &revwalk_enqueue_unsorted; walk->repo = repo; - error = git_repository_odb(&walk->odb, repo); - if (error < GIT_SUCCESS) { + if (git_repository_odb(&walk->odb, repo) < 0) { git_revwalk_free(walk); - return error; + return -1; } *revwalk_out = walk; - return GIT_SUCCESS; + return 0; } void git_revwalk_free(git_revwalk *walk) { - unsigned int i; - commit_object *commit; - if (walk == NULL) return; git_revwalk_reset(walk); git_odb_free(walk->odb); - /* if the parent has more than PARENTS_PER_COMMIT parents, - * we had to allocate a separate array for those parents. - * make sure it's being free'd */ - GIT_HASHTABLE_FOREACH_VALUE(walk->commits, commit, { - if (commit->out_degree > PARENTS_PER_COMMIT) - git__free(commit->parents); - }); - - git_hashtable_free(walk->commits); + git_oidmap_free(walk->commits); + git_pool_clear(&walk->commit_pool); git_pqueue_free(&walk->iterator_time); - - for (i = 0; i < walk->memory_alloc.length; ++i) - git__free(git_vector_get(&walk->memory_alloc, i)); - - git_vector_free(&walk->memory_alloc); + git_vector_free(&walk->twos); git__free(walk); } @@ -648,8 +803,8 @@ int git_revwalk_next(git_oid *oid, git_revwalk *walk) assert(walk && oid); if (!walk->walking) { - if ((error = prepare_walk(walk)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to load next revision"); + if ((error = prepare_walk(walk)) < 0) + return error; } error = walk->get_next(&next, walk); @@ -659,11 +814,10 @@ int git_revwalk_next(git_oid *oid, git_revwalk *walk) return GIT_EREVWALKOVER; } - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to load next revision"); + if (!error) + git_oid_cpy(oid, &next->oid); - git_oid_cpy(oid, &next->oid); - return GIT_SUCCESS; + return error; } void git_revwalk_reset(git_revwalk *walk) @@ -672,12 +826,12 @@ void git_revwalk_reset(git_revwalk *walk) assert(walk); - GIT_HASHTABLE_FOREACH_VALUE(walk->commits, commit, + kh_foreach_value(walk->commits, commit, { commit->seen = 0; commit->in_degree = 0; commit->topo_delay = 0; commit->uninteresting = 0; - ); + }); git_pqueue_clear(&walk->iterator_time); commit_list_free(&walk->iterator_topo); diff --git a/src/sha1.c b/src/sha1.c index af3c2d2d0..8aaedeb8f 100644 --- a/src/sha1.c +++ b/src/sha1.c @@ -232,7 +232,7 @@ void git__blk_SHA1_Init(blk_SHA_CTX *ctx) ctx->H[4] = 0xc3d2e1f0; } -void git__blk_SHA1_Update(blk_SHA_CTX *ctx, const void *data, unsigned long len) +void git__blk_SHA1_Update(blk_SHA_CTX *ctx, const void *data, size_t len) { unsigned int lenW = ctx->size & 63; @@ -242,7 +242,7 @@ void git__blk_SHA1_Update(blk_SHA_CTX *ctx, const void *data, unsigned long len) if (lenW) { unsigned int left = 64 - lenW; if (len < left) - left = len; + left = (unsigned int)len; memcpy(lenW + (char *)ctx->W, data, left); lenW = (lenW + left) & 63; len -= left; diff --git a/src/sha1.h b/src/sha1.h index 117e93106..93a244d76 100644 --- a/src/sha1.h +++ b/src/sha1.h @@ -12,7 +12,7 @@ typedef struct { } blk_SHA_CTX; void git__blk_SHA1_Init(blk_SHA_CTX *ctx); -void git__blk_SHA1_Update(blk_SHA_CTX *ctx, const void *dataIn, unsigned long len); +void git__blk_SHA1_Update(blk_SHA_CTX *ctx, const void *dataIn, size_t len); void git__blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx); #define SHA_CTX blk_SHA_CTX diff --git a/src/sha1_lookup.c b/src/sha1_lookup.c index 58d70aeb7..096da1739 100644 --- a/src/sha1_lookup.c +++ b/src/sha1_lookup.c @@ -158,7 +158,8 @@ int sha1_entry_pos(const void *table, #endif if (!(lo <= mi && mi < hi)) { - return git__throw(GIT_ERROR, "Assertion failure. Binary search invariant is false"); + giterr_set(GITERR_INVALID, "Assertion failure. Binary search invariant is false"); + return -1; } mi_key = base + elem_size * mi + key_offset; diff --git a/src/signature.c b/src/signature.c index 1b6ba2149..4d6d11c70 100644 --- a/src/signature.c +++ b/src/signature.c @@ -38,31 +38,38 @@ static const char *skip_trailing_spaces(const char *buffer_start, const char *bu return buffer_end; } +static int signature_error(const char *msg) +{ + giterr_set(GITERR_INVALID, "Failed to parse signature - %s", msg); + return -1; +} + static int process_trimming(const char *input, char **storage, const char *input_end, int fail_when_empty) { const char *left, *right; - int trimmed_input_length; + size_t trimmed_input_length; + + assert(storage); left = skip_leading_spaces(input, input_end); right = skip_trailing_spaces(input, input_end - 1); if (right < left) { if (fail_when_empty) - return git__throw(GIT_EINVALIDARGS, "Failed to trim. Input is either empty or only contains spaces"); - else - right = left - 1; + return signature_error("input is either empty of contains only spaces"); + + right = left - 1; } trimmed_input_length = right - left + 1; *storage = git__malloc(trimmed_input_length + 1); - if (*storage == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(*storage); memcpy(*storage, left, trimmed_input_length); (*storage)[trimmed_input_length] = 0; - return GIT_SUCCESS; + return 0; } int git_signature_new(git_signature **sig_out, const char *name, const char *email, git_time_t time, int offset) @@ -74,23 +81,14 @@ int git_signature_new(git_signature **sig_out, const char *name, const char *ema *sig_out = NULL; - if ((p = git__malloc(sizeof(git_signature))) == NULL) { - error = GIT_ENOMEM; - goto cleanup; - } - - memset(p, 0x0, sizeof(git_signature)); - - error = process_trimming(name, &p->name, name + strlen(name), 1); - if (error < GIT_SUCCESS) { - git__rethrow(GIT_EINVALIDARGS, "Failed to create signature. 'name' argument is invalid"); - goto cleanup; - } + p = git__calloc(1, sizeof(git_signature)); + GITERR_CHECK_ALLOC(p); - error = process_trimming(email, &p->email, email + strlen(email), 1); - if (error < GIT_SUCCESS) { - git__rethrow(GIT_EINVALIDARGS, "Failed to create signature. 'email' argument is invalid"); - goto cleanup; + if ((error = process_trimming(name, &p->name, name + strlen(name), 1)) < 0 || + (error = process_trimming(email, &p->email, email + strlen(email), 1)) < 0) + { + git_signature_free(p); + return error; } p->when.time = time; @@ -98,24 +96,19 @@ int git_signature_new(git_signature **sig_out, const char *name, const char *ema *sig_out = p; - return error; - -cleanup: - git_signature_free(p); - return error; + return 0; } git_signature *git_signature_dup(const git_signature *sig) { git_signature *new; - if (git_signature_new(&new, sig->name, sig->email, sig->when.time, sig->when.offset) < GIT_SUCCESS) + if (git_signature_new(&new, sig->name, sig->email, sig->when.time, sig->when.offset) < 0) return NULL; return new; } int git_signature_now(git_signature **sig_out, const char *name, const char *email) { - int error; time_t now; time_t offset; struct tm *utc_tm, *local_tm; @@ -148,12 +141,18 @@ int git_signature_now(git_signature **sig_out, const char *name, const char *ema if (local_tm->tm_isdst) offset += 60; - if ((error = git_signature_new(&sig, name, email, now, (int)offset)) < GIT_SUCCESS) - return error; + if (git_signature_new(&sig, name, email, now, (int)offset) < 0) + return -1; *sig_out = sig; - return error; + return 0; +} + +static int timezone_error(const char *msg) +{ + giterr_set(GITERR_INVALID, "Failed to parse TZ offset - %s", msg); + return -1; } static int parse_timezone_offset(const char *buffer, int *offset_out) @@ -172,28 +171,28 @@ static int parse_timezone_offset(const char *buffer, int *offset_out) } if (offset_start[0] != '-' && offset_start[0] != '+') - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. It doesn't start with '+' or '-'"); + return timezone_error("does not start with '+' or '-'"); if (offset_start[1] < '0' || offset_start[1] > '9') - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset."); + return timezone_error("expected initial digit"); if (git__strtol32(&dec_offset, offset_start + 1, &offset_end, 10) < GIT_SUCCESS) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. It isn't a number"); + return timezone_error("not a valid number"); if (offset_end - offset_start != 5) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. Invalid length"); + return timezone_error("invalid length"); if (dec_offset > 1400) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. Value too large"); + return timezone_error("value too large"); hours = dec_offset / 100; mins = dec_offset % 100; if (hours > 14) // see http://www.worldtimezone.com/faq.html - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. Hour value too large"); + return timezone_error("hour value too large"); if (mins > 59) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse TZ offset. Minute value too large"); + return timezone_error("minutes value too large"); offset = (hours * 60) + mins; @@ -202,22 +201,22 @@ static int parse_timezone_offset(const char *buffer, int *offset_out) *offset_out = offset; - return GIT_SUCCESS; + return 0; } static int process_next_token(const char **buffer_out, char **storage, const char *token_end, const char *right_boundary) { int error = process_trimming(*buffer_out, storage, token_end, 0); - if (error < GIT_SUCCESS) + if (error < 0) return error; *buffer_out = token_end + 1; if (*buffer_out > right_boundary) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Signature too short"); + return signature_error("signature is too short"); - return GIT_SUCCESS; + return 0; } static const char *scan_for_previous_token(const char *buffer, const char *left_boundary) @@ -241,17 +240,17 @@ static int parse_time(git_time_t *time_out, const char *buffer) int time; int error; - if (*buffer == '+' || *buffer == '-') - return git__throw(GIT_ERROR, "Failed while parsing time. '%s' rather look like a timezone offset.", buffer); + if (*buffer == '+' || *buffer == '-') { + giterr_set(GITERR_INVALID, "Failed while parsing time. '%s' actually looks like a timezone offset.", buffer); + return -1; + } error = git__strtol32(&time, buffer, &buffer, 10); - if (error < GIT_SUCCESS) - return error; + if (!error) + *time_out = (git_time_t)time; - *time_out = (git_time_t)time; - - return GIT_SUCCESS; + return error; } int git_signature__parse(git_signature *sig, const char **buffer_out, @@ -259,40 +258,40 @@ int git_signature__parse(git_signature *sig, const char **buffer_out, { const char *buffer = *buffer_out; const char *line_end, *name_end, *email_end, *tz_start, *time_start; - int error = GIT_SUCCESS; + int error = 0; memset(sig, 0x0, sizeof(git_signature)); if ((line_end = memchr(buffer, ender, buffer_end - buffer)) == NULL) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. No newline given"); + return signature_error("no newline given"); if (header) { const size_t header_len = strlen(header); if (memcmp(buffer, header, header_len) != 0) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Expected prefix '%s' doesn't match actual", header); + return signature_error("expected prefix doesn't match actual"); buffer += header_len; } if (buffer > line_end) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Signature too short"); + return signature_error("signature too short"); if ((name_end = strchr(buffer, '<')) == NULL) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Cannot find '<' in signature"); + return signature_error("character '<' not allowed in signature"); if ((email_end = strchr(name_end, '>')) == NULL) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Cannot find '>' in signature"); + return signature_error("character '>' not allowed in signature"); if (email_end < name_end) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Malformed e-mail"); + return signature_error("malformed e-mail"); error = process_next_token(&buffer, &sig->name, name_end, line_end); - if (error < GIT_SUCCESS) + if (error < 0) return error; error = process_next_token(&buffer, &sig->email, email_end, line_end); - if (error < GIT_SUCCESS) + if (error < 0) return error; tz_start = scan_for_previous_token(line_end - 1, buffer); @@ -301,19 +300,19 @@ int git_signature__parse(git_signature *sig, const char **buffer_out, goto clean_exit; /* No timezone nor date */ time_start = scan_for_previous_token(tz_start - 1, buffer); - if (time_start == NULL || parse_time(&sig->when.time, time_start) < GIT_SUCCESS) { + if (time_start == NULL || parse_time(&sig->when.time, time_start) < 0) { /* The tz_start might point at the time */ parse_time(&sig->when.time, tz_start); goto clean_exit; } - if (parse_timezone_offset(tz_start, &sig->when.offset) < GIT_SUCCESS) { + if (parse_timezone_offset(tz_start, &sig->when.offset) < 0) { sig->when.time = 0; /* Bogus timezone, we reset the time */ } clean_exit: *buffer_out = line_end + 1; - return GIT_SUCCESS; + return 0; } void git_signature__writebuf(git_buf *buf, const char *header, const git_signature *sig) diff --git a/src/status.c b/src/status.c index 106e5005d..356cbeb98 100644 --- a/src/status.c +++ b/src/status.c @@ -15,6 +15,197 @@ #include "repository.h" #include "ignore.h" +#include "git2/diff.h" +#include "diff.h" + +static int resolve_head_to_tree(git_tree **tree, git_repository *repo) +{ + git_oid head_oid; + git_object *obj = NULL; + + if (git_reference_name_to_oid(&head_oid, repo, GIT_HEAD_FILE) < 0) { + /* cannot resolve HEAD - probably brand new repo */ + giterr_clear(); + *tree = NULL; + return 0; + } + + if (git_object_lookup(&obj, repo, &head_oid, GIT_OBJ_ANY) < 0) + goto fail; + + switch (git_object_type(obj)) { + case GIT_OBJ_TREE: + *tree = (git_tree *)obj; + break; + case GIT_OBJ_COMMIT: + if (git_commit_tree(tree, (git_commit *)obj) < 0) + goto fail; + git_object_free(obj); + break; + default: + goto fail; + } + + return 0; + +fail: + git_object_free(obj); + return -1; +} + +static unsigned int index_delta2status(git_delta_t index_status) +{ + unsigned int st = GIT_STATUS_CURRENT; + + switch (index_status) { + case GIT_DELTA_ADDED: + case GIT_DELTA_COPIED: + case GIT_DELTA_RENAMED: + st = GIT_STATUS_INDEX_NEW; + break; + case GIT_DELTA_DELETED: + st = GIT_STATUS_INDEX_DELETED; + break; + case GIT_DELTA_MODIFIED: + st = GIT_STATUS_INDEX_MODIFIED; + break; + default: + break; + } + + return st; +} + +static unsigned int workdir_delta2status(git_delta_t workdir_status) +{ + unsigned int st = GIT_STATUS_CURRENT; + + switch (workdir_status) { + case GIT_DELTA_ADDED: + case GIT_DELTA_COPIED: + case GIT_DELTA_RENAMED: + case GIT_DELTA_UNTRACKED: + st = GIT_STATUS_WT_NEW; + break; + case GIT_DELTA_DELETED: + st = GIT_STATUS_WT_DELETED; + break; + case GIT_DELTA_MODIFIED: + st = GIT_STATUS_WT_MODIFIED; + break; + case GIT_DELTA_IGNORED: + st = GIT_STATUS_IGNORED; + break; + default: + break; + } + + return st; +} + +int git_status_foreach_ext( + git_repository *repo, + git_status_options *opts, + int (*cb)(const char *, unsigned int, void *), + void *cbdata) +{ + int err = 0, cmp; + git_diff_options diffopt; + git_diff_list *idx2head = NULL, *wd2idx = NULL; + git_tree *head = NULL; + git_status_show_t show = + opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + git_diff_delta *i2h, *w2i; + unsigned int i, j, i_max, j_max; + + assert(show <= GIT_STATUS_SHOW_INDEX_THEN_WORKDIR); + + switch (resolve_head_to_tree(&head, repo)) { + case 0: break; + case GIT_ENOTFOUND: return 0; + default: return -1; + } + + memset(&diffopt, 0, sizeof(diffopt)); + memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec)); + + if ((opts->flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED; + if ((opts->flags & GIT_STATUS_OPT_INCLUDE_IGNORED) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_IGNORED; + if ((opts->flags & GIT_STATUS_OPT_INCLUDE_UNMODIFIED) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNMODIFIED; + if ((opts->flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS; + /* TODO: support EXCLUDE_SUBMODULES flag */ + + if (show != GIT_STATUS_SHOW_WORKDIR_ONLY && head != NULL && + (err = git_diff_index_to_tree(repo, &diffopt, head, &idx2head)) < 0) + goto cleanup; + + if (show != GIT_STATUS_SHOW_INDEX_ONLY && + (err = git_diff_workdir_to_index(repo, &diffopt, &wd2idx)) < 0) + goto cleanup; + + if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) { + for (i = 0; !err && i < idx2head->deltas.length; i++) { + i2h = GIT_VECTOR_GET(&idx2head->deltas, i); + err = cb(i2h->old_file.path, index_delta2status(i2h->status), cbdata); + } + git_diff_list_free(idx2head); + idx2head = NULL; + } + + i_max = idx2head ? idx2head->deltas.length : 0; + j_max = wd2idx ? wd2idx->deltas.length : 0; + + for (i = 0, j = 0; !err && (i < i_max || j < j_max); ) { + i2h = idx2head ? GIT_VECTOR_GET(&idx2head->deltas,i) : NULL; + w2i = wd2idx ? GIT_VECTOR_GET(&wd2idx->deltas,j) : NULL; + + cmp = !w2i ? -1 : !i2h ? 1 : strcmp(i2h->old_file.path, w2i->old_file.path); + + if (cmp < 0) { + err = cb(i2h->old_file.path, index_delta2status(i2h->status), cbdata); + i++; + } else if (cmp > 0) { + err = cb(w2i->old_file.path, workdir_delta2status(w2i->status), cbdata); + j++; + } else { + err = cb(i2h->old_file.path, index_delta2status(i2h->status) | + workdir_delta2status(w2i->status), cbdata); + i++; j++; + } + } + +cleanup: + git_tree_free(head); + git_diff_list_free(idx2head); + git_diff_list_free(wd2idx); + return err; +} + +int git_status_foreach( + git_repository *repo, + int (*callback)(const char *, unsigned int, void *), + void *payload) +{ + git_status_options opts; + + memset(&opts, 0, sizeof(opts)); + opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED | + GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + + return git_status_foreach_ext(repo, &opts, callback, payload); +} + + +/* + * the old stuff + */ + struct status_entry { git_index_time mtime; @@ -76,15 +267,17 @@ static int status_entry_update_from_workdir(struct status_entry *e, const char* { struct stat filest; - if (p_stat(full_path, &filest) < GIT_SUCCESS) - return git__throw(GIT_EOSERR, "Failed to determine status of file '%s'. Can't read file", full_path); + if (p_stat(full_path, &filest) < 0) { + giterr_set(GITERR_OS, "Cannot access file '%s'", full_path); + return GIT_ENOTFOUND; + } if (e->mtime.seconds == (git_time_t)filest.st_mtime) git_oid_cpy(&e->wt_oid, &e->index_oid); else git_odb_hashfile(&e->wt_oid, full_path, GIT_OBJ_BLOB); - return GIT_SUCCESS; + return 0; } static int status_entry_update_flags(struct status_entry *e) @@ -115,7 +308,7 @@ static int status_entry_update_flags(struct status_entry *e) else if (git_oid_cmp(&e->index_oid, &e->wt_oid) != 0) e->status_flags |= GIT_STATUS_WT_MODIFIED; - return GIT_SUCCESS; + return 0; } static int status_entry_is_ignorable(struct status_entry *e) @@ -126,432 +319,17 @@ static int status_entry_is_ignorable(struct status_entry *e) static int status_entry_update_ignore(struct status_entry *e, git_ignores *ignores, const char *path) { - int error, ignored; - - if ((error = git_ignore__lookup(ignores, path, &ignored)) == GIT_SUCCESS && - ignored) - e->status_flags = - (e->status_flags & ~GIT_STATUS_WT_NEW) | GIT_STATUS_IGNORED; - - return error; -} - -struct status_st { - git_repository *repo; - git_vector *vector; - git_index *index; - git_tree *tree; - git_ignores *ignores; - - int workdir_path_len; - git_buf head_tree_relative_path; - int head_tree_relative_path_len; - unsigned int tree_position; - unsigned int index_position; - int is_dir:1; -}; - -static int retrieve_head_tree(git_tree **tree_out, git_repository *repo) -{ - git_reference *resolved_head_ref; - git_commit *head_commit = NULL; - git_tree *tree; - int error = GIT_SUCCESS; - - *tree_out = NULL; - - error = git_repository_head(&resolved_head_ref, repo); - /* - * We assume that a situation where HEAD exists but can not be resolved is valid. - * A new repository fits this description for instance. - */ - if (error == GIT_ENOTFOUND) - return GIT_SUCCESS; - if (error < GIT_SUCCESS) - return git__rethrow(error, "HEAD can't be resolved"); - - if ((error = git_commit_lookup(&head_commit, repo, git_reference_oid(resolved_head_ref))) < GIT_SUCCESS) - return git__rethrow(error, "The tip of HEAD can't be retrieved"); - - git_reference_free(resolved_head_ref); - - if ((error = git_commit_tree(&tree, head_commit)) < GIT_SUCCESS) { - error = git__rethrow(error, "The tree of HEAD can't be retrieved"); - goto exit; - } - - *tree_out = tree; - -exit: - git_commit_free(head_commit); - return error; -} - -enum path_type { - GIT_STATUS_PATH_NULL, - GIT_STATUS_PATH_IGNORE, - GIT_STATUS_PATH_FILE, - GIT_STATUS_PATH_FOLDER, -}; - -static int dirent_cb(void *state, git_buf *full_path); -static int alphasorted_futils_direach( - git_buf *path, int (*fn)(void *, git_buf *), void *arg); - -static int process_folder( - struct status_st *st, - const git_tree_entry *tree_entry, - git_buf *full_path, - enum path_type path_type) -{ - git_object *subtree = NULL; - git_tree *pushed_tree = NULL; - int error, pushed_tree_position = 0; - git_otype tree_entry_type = GIT_OBJ_BAD; - - if (tree_entry != NULL) { - tree_entry_type = git_tree_entry_type(tree_entry); - - switch (tree_entry_type) { - case GIT_OBJ_TREE: - error = git_tree_entry_2object(&subtree, ((git_object *)(st->tree))->repo, tree_entry); - pushed_tree = st->tree; - pushed_tree_position = st->tree_position; - st->tree = (git_tree *)subtree; - st->tree_position = 0; - st->head_tree_relative_path_len += 1 + tree_entry->filename_len; /* path + '/' + name */ - break; - - case GIT_OBJ_BLOB: - /* No op */ - break; - - case GIT_OBJ_COMMIT: - /* TODO: proper submodule support */ - break; - - default: - return git__throw(GIT_EINVALIDTYPE, "Unexpected tree entry type"); - } - } - - - if (full_path != NULL && path_type == GIT_STATUS_PATH_FOLDER) { - git_ignores ignores, *old_ignores; - - if ((error = git_ignore__for_path(st->repo, - full_path->ptr + st->workdir_path_len, &ignores)) == GIT_SUCCESS) - { - old_ignores = st->ignores; - st->ignores = &ignores; - - error = alphasorted_futils_direach(full_path, dirent_cb, st); - - git_ignore__free(st->ignores); - st->ignores = old_ignores; - } - } else { - error = dirent_cb(st, NULL); - } - - if (tree_entry_type == GIT_OBJ_TREE) { - git_object_free(subtree); - st->head_tree_relative_path_len -= 1 + tree_entry->filename_len; - st->tree = pushed_tree; - st->tree_position = pushed_tree_position; - st->tree_position++; - } - - return error; -} - -static int store_if_changed(struct status_st *st, struct status_entry *e) -{ - int error; - if ((error = status_entry_update_flags(e)) < GIT_SUCCESS) - return git__throw(error, "Failed to process the file '%s'. It doesn't exist in the workdir, in the HEAD nor in the index", e->path); - - if (status_entry_is_ignorable(e) && - (error = status_entry_update_ignore(e, st->ignores, e->path)) < GIT_SUCCESS) - return error; - - if (e->status_flags == GIT_STATUS_CURRENT) { - git__free(e); - return GIT_SUCCESS; - } - - return git_vector_insert(st->vector, e); -} - -static int determine_status( - struct status_st *st, - int in_head, int in_index, int in_workdir, - const git_tree_entry *tree_entry, - const git_index_entry *index_entry, - git_buf *full_path, - const char *status_path, - enum path_type path_type) -{ - struct status_entry *e; - int error = GIT_SUCCESS; - git_otype tree_entry_type = GIT_OBJ_BAD; - - if (tree_entry != NULL) - tree_entry_type = git_tree_entry_type(tree_entry); - - /* If we're dealing with a directory in the workdir, let's recursively tackle it first */ - if (path_type == GIT_STATUS_PATH_FOLDER) - return process_folder(st, tree_entry, full_path, path_type); - - /* Are we dealing with a file somewhere? */ - if (in_workdir || in_index || (in_head && tree_entry_type == GIT_OBJ_BLOB)) { - e = status_entry_new(NULL, status_path); - - if (in_head && tree_entry_type == GIT_OBJ_BLOB) { - status_entry_update_from_tree_entry(e, tree_entry); - st->tree_position++; - } - - if (in_index) { - status_entry_update_from_index_entry(e, index_entry); - st->index_position++; - } - - if (in_workdir) - if ((error = status_entry_update_from_workdir( - e, full_path->ptr)) < GIT_SUCCESS) - return error; /* The callee has already set the error message */ - - return store_if_changed(st, e); - } - - /* Are we dealing with a subtree? */ - if (tree_entry_type == GIT_OBJ_TREE) { - assert(in_head && !in_index && !in_workdir); - return process_folder(st, tree_entry, full_path, path_type); - } - - /* We're dealing with something else -- most likely a submodule; - * skip it for now */ - if (in_head) - st->tree_position++; - if (in_index) - st->index_position++; - return GIT_SUCCESS; -} - -static int path_type_from(git_buf *full_path, int is_dir) -{ - if (full_path == NULL) - return GIT_STATUS_PATH_NULL; + int ignored; - if (!is_dir) - return GIT_STATUS_PATH_FILE; - - if (!git__suffixcmp(full_path->ptr, "/" DOT_GIT "/")) - return GIT_STATUS_PATH_IGNORE; - - return GIT_STATUS_PATH_FOLDER; -} - -static const char *status_path(const char *first, const char *second, const char *third) -{ - /* At least one of them can not be NULL */ - assert(first != NULL || second != NULL || third != NULL); - - /* TODO: Fixme. Ensure that when non null, they're all equal */ - if (first != NULL) - return first; - - if (second != NULL) - return second; - - return third; -} - -static int compare(const char *left, const char *right) -{ - if (left == NULL && right == NULL) - return 0; - - if (left == NULL) - return 1; - - if (right == NULL) + if (git_ignore__lookup(ignores, path, &ignored) < 0) return -1; - return strcmp(left, right); -} - -/* Greatly inspired from JGit IndexTreeWalker */ -/* https://github.com/spearce/jgit/blob/ed47e29c777accfa78c6f50685a5df2b8f5b8ff5/org.spearce.jgit/src/org/spearce/jgit/lib/IndexTreeWalker.java#L88 */ - -static int dirent_cb(void *state, git_buf *a) -{ - const git_tree_entry *m; - const git_index_entry *entry; - enum path_type path_type; - int cmpma, cmpmi, cmpai, error; - const char *pm, *pa, *pi; - const char *m_name, *i_name, *a_name; - struct status_st *st = (struct status_st *)state; - - path_type = path_type_from(a, st->is_dir); - - if (path_type == GIT_STATUS_PATH_IGNORE) - return GIT_SUCCESS; /* Let's skip the ".git" directory */ - - a_name = (path_type != GIT_STATUS_PATH_NULL) ? a->ptr + st->workdir_path_len : NULL; - - while (1) { - if (st->tree == NULL) - m = NULL; - else - m = git_tree_entry_byindex(st->tree, st->tree_position); - - entry = git_index_get(st->index, st->index_position); - - if ((m == NULL) && (a == NULL) && (entry == NULL)) - return GIT_SUCCESS; - - if (m != NULL) { - git_buf_truncate(&st->head_tree_relative_path, - st->head_tree_relative_path_len); - git_buf_joinpath(&st->head_tree_relative_path, - st->head_tree_relative_path.ptr, m->filename); - /* When the tree entry is a folder, append a forward slash to its name */ - if (git_tree_entry_type(m) == GIT_OBJ_TREE) - git_path_to_dir(&st->head_tree_relative_path); - - error = git_buf_lasterror(&st->head_tree_relative_path); - if (error < GIT_SUCCESS) - return git__rethrow(error, "An error occured while " - "determining the status of '%s'", a->ptr); - - m_name = st->head_tree_relative_path.ptr; - } else - m_name = NULL; - - i_name = (entry != NULL) ? entry->path : NULL; - - cmpma = compare(m_name, a_name); - cmpmi = compare(m_name, i_name); - cmpai = compare(a_name, i_name); - - pm = ((cmpma <= 0) && (cmpmi <= 0)) ? m_name : NULL; - pa = ((cmpma >= 0) && (cmpai <= 0)) ? a_name : NULL; - pi = ((cmpmi >= 0) && (cmpai >= 0)) ? i_name : NULL; - - if ((error = determine_status(st, pm != NULL, pi != NULL, pa != NULL, - m, entry, a, status_path(pm, pi, pa), path_type)) < GIT_SUCCESS) - return git__rethrow(error, "An error occured while determining the status of '%s'", a->ptr); - - if ((pa != NULL) || (path_type == GIT_STATUS_PATH_FOLDER)) - return GIT_SUCCESS; - } -} - -static int status_cmp(const void *a, const void *b) -{ - const struct status_entry *entry_a = (const struct status_entry *)(a); - const struct status_entry *entry_b = (const struct status_entry *)(b); - - return strcmp(entry_a->path, entry_b->path); -} - -#define DEFAULT_SIZE 16 - -int git_status_foreach( - git_repository *repo, - int (*callback)(const char *, unsigned int, void *), - void *payload) -{ - git_vector entries; - git_ignores ignores; - git_index *index = NULL; - git_buf temp_path = GIT_BUF_INIT; - struct status_st dirent_st = {0}; - int error = GIT_SUCCESS; - unsigned int i; - git_tree *tree; - struct status_entry *e; - const char *workdir; - - if ((workdir = git_repository_workdir(repo)) == NULL) - return git__throw(GIT_ERROR, - "Cannot retrieve status on a bare repository"); - - if ((error = git_repository_index__weakptr(&index, repo)) < GIT_SUCCESS) { - return git__rethrow(error, - "Failed to determine statuses. Index can't be opened"); - } - - if ((error = retrieve_head_tree(&tree, repo)) < GIT_SUCCESS) { - error = git__rethrow(error, "Failed to determine statuses"); - goto exit; - } - - git_vector_init(&entries, DEFAULT_SIZE, status_cmp); - - dirent_st.repo = repo; - dirent_st.vector = &entries; - dirent_st.index = index; - dirent_st.tree = tree; - dirent_st.ignores = &ignores; - dirent_st.workdir_path_len = strlen(workdir); - git_buf_init(&dirent_st.head_tree_relative_path, 0); - dirent_st.head_tree_relative_path_len = 0; - dirent_st.tree_position = 0; - dirent_st.index_position = 0; - dirent_st.is_dir = 1; - - if (git_path_isdir(workdir)) { - error = git__throw(GIT_EINVALIDPATH, - "Failed to determine status of file '%s'. " - "The given path doesn't lead to a folder", workdir); - goto exit; - } - - git_buf_sets(&temp_path, workdir); - - error = git_ignore__for_path(repo, "", dirent_st.ignores); - if (error < GIT_SUCCESS) - goto exit; - - error = alphasorted_futils_direach( - &temp_path, dirent_cb, &dirent_st); - - if (error < GIT_SUCCESS) - error = git__rethrow(error, - "Failed to determine statuses. " - "An error occured while processing the working directory"); - - if ((error == GIT_SUCCESS) && - ((error = dirent_cb(&dirent_st, NULL)) < GIT_SUCCESS)) - error = git__rethrow(error, - "Failed to determine statuses. " - "An error occured while post-processing the HEAD tree and the index"); - - for (i = 0; i < entries.length; ++i) { - e = (struct status_entry *)git_vector_get(&entries, i); - - if (error == GIT_SUCCESS) { - error = callback(e->path, e->status_flags, payload); - if (error < GIT_SUCCESS) - error = git__rethrow(error, - "Failed to determine statuses. User callback failed"); - } - - git__free(e); - } + if (ignored) + /* toggle off WT_NEW and on IGNORED */ + e->status_flags = + (e->status_flags & ~GIT_STATUS_WT_NEW) | GIT_STATUS_IGNORED; -exit: - git_buf_free(&dirent_st.head_tree_relative_path); - git_buf_free(&temp_path); - git_vector_free(&entries); - git_ignore__free(&ignores); - git_tree_free(tree); - return error; + return 0; } static int recurse_tree_entry(git_tree *tree, struct status_entry *e, const char *path) @@ -559,236 +337,117 @@ static int recurse_tree_entry(git_tree *tree, struct status_entry *e, const char char *dir_sep; const git_tree_entry *tree_entry; git_tree *subtree; - int error = GIT_SUCCESS; + int error; dir_sep = strchr(path, '/'); if (!dir_sep) { - tree_entry = git_tree_entry_byname(tree, path); - if (tree_entry == NULL) - return GIT_SUCCESS; /* The leaf doesn't exist in the tree*/ - - status_entry_update_from_tree_entry(e, tree_entry); - return GIT_SUCCESS; + if ((tree_entry = git_tree_entry_byname(tree, path)) != NULL) + /* The leaf exists in the tree*/ + status_entry_update_from_tree_entry(e, tree_entry); + return 0; } /* Retrieve subtree name */ *dir_sep = '\0'; - tree_entry = git_tree_entry_byname(tree, path); - if (tree_entry == NULL) - return GIT_SUCCESS; /* The subtree doesn't exist in the tree*/ + if ((tree_entry = git_tree_entry_byname(tree, path)) == NULL) + return 0; /* The subtree doesn't exist in the tree*/ *dir_sep = '/'; /* Retreive subtree */ - if ((error = git_tree_lookup(&subtree, tree->object.repo, &tree_entry->oid)) < GIT_SUCCESS) - return git__throw(GIT_EOBJCORRUPTED, "Can't find tree object '%s'", tree_entry->filename); + error = git_tree_lookup(&subtree, tree->object.repo, &tree_entry->oid); + if (!error) { + error = recurse_tree_entry(subtree, e, dir_sep+1); + git_tree_free(subtree); + } - error = recurse_tree_entry(subtree, e, dir_sep+1); - git_tree_free(subtree); return error; } -int git_status_file(unsigned int *status_flags, git_repository *repo, const char *path) +int git_status_file( + unsigned int *status_flags, git_repository *repo, const char *path) { struct status_entry *e; git_index *index = NULL; git_buf temp_path = GIT_BUF_INIT; - int error = GIT_SUCCESS; + int error = 0; git_tree *tree = NULL; const char *workdir; assert(status_flags && repo && path); - if ((workdir = git_repository_workdir(repo)) == NULL) - return git__throw(GIT_ERROR, - "Cannot retrieve status on a bare repository"); + if ((workdir = git_repository_workdir(repo)) == NULL) { + giterr_set(GITERR_OS, "Cannot get file status from bare repo"); + return GIT_ENOTFOUND; + } - if ((error = git_buf_joinpath(&temp_path, workdir, path)) < GIT_SUCCESS) - return git__rethrow(error, - "Failed to determine status of file '%s'", path); + if (git_buf_joinpath(&temp_path, workdir, path) < 0) + return -1; - if (git_path_isdir(temp_path.ptr) == GIT_SUCCESS) { + if (git_path_isdir(temp_path.ptr)) { + giterr_set(GITERR_OS, "Cannot get file status for directory '%s'", temp_path.ptr); git_buf_free(&temp_path); - return git__throw(GIT_EINVALIDPATH, - "Failed to determine status of file '%s'. " - "Given path leads to a folder, not a file", path); + return GIT_ENOTFOUND; } e = status_entry_new(NULL, path); - if (e == NULL) { - git_buf_free(&temp_path); - return GIT_ENOMEM; - } + GITERR_CHECK_ALLOC(e); /* Find file in Workdir */ - if (git_path_exists(temp_path.ptr) == GIT_SUCCESS) { - if ((error = status_entry_update_from_workdir(e, temp_path.ptr)) < GIT_SUCCESS) - goto cleanup; /* The callee has already set the error message */ - } + if (git_path_exists(temp_path.ptr) == true && + (error = status_entry_update_from_workdir(e, temp_path.ptr)) < 0) + goto cleanup; /* Find file in Index */ - if ((error = git_repository_index__weakptr(&index, repo)) < GIT_SUCCESS) { - git__rethrow(error, - "Failed to determine status of file '%s'." - "Index can't be opened", path); + if ((error = git_repository_index__weakptr(&index, repo)) < 0) goto cleanup; - } - status_entry_update_from_index(e, index); - if ((error = retrieve_head_tree(&tree, repo)) < GIT_SUCCESS) { - git__rethrow(error, - "Failed to determine status of file '%s'", path); + /* Try to find file in HEAD */ + if ((error = resolve_head_to_tree(&tree, repo)) < 0) goto cleanup; - } - /* If the repository is not empty, try and locate the file in HEAD */ if (tree != NULL) { - if ((error = git_buf_sets(&temp_path, path)) < GIT_SUCCESS) { - git__rethrow(error, - "Failed to determine status of file '%s'", path); + if ((error = git_buf_sets(&temp_path, path)) < 0 || + (error = recurse_tree_entry(tree, e, temp_path.ptr)) < 0) goto cleanup; - } - - error = recurse_tree_entry(tree, e, temp_path.ptr); - if (error < GIT_SUCCESS) { - git__rethrow(error, - "Failed to determine status of file '%s'. " - "An error occured while processing the tree", path); - goto cleanup; - } } /* Determine status */ - if ((error = status_entry_update_flags(e)) < GIT_SUCCESS) { - git__throw(error, "Nonexistent file"); - goto cleanup; - } + if ((error = status_entry_update_flags(e)) < 0) + giterr_set(GITERR_OS, "Cannot find file '%s' to determine status", path); - if (status_entry_is_ignorable(e)) { + if (!error && status_entry_is_ignorable(e)) { git_ignores ignores; - if ((error = git_ignore__for_path(repo, path, &ignores)) == GIT_SUCCESS) + if ((error = git_ignore__for_path(repo, path, &ignores)) == 0) error = status_entry_update_ignore(e, &ignores, path); git_ignore__free(&ignores); - - if (error < GIT_SUCCESS) - goto cleanup; } - *status_flags = e->status_flags; + if (!error) + *status_flags = e->status_flags; cleanup: git_buf_free(&temp_path); git_tree_free(tree); git__free(e); - return error; -} - -/* - * git_path_direach is not supposed to return entries in an ordered manner. - * alphasorted_futils_direach wraps git_path_direach and invokes the callback - * function by passing it alphabeticcally sorted paths parameters. - * - */ - -static char *alphasorted_dirent_info_new(const git_buf *path) -{ - char *di = git__malloc(path->size + 2); - if (!di) - return di; - - git_buf_copy_cstr(di, path->size + 1, path); - - if (git_path_isdir(path->ptr) == GIT_SUCCESS) { - /* - * Append a forward slash to the name to force folders - * to be ordered in a similar way than in a tree - * - * The file "subdir" should appear before the file "subdir.txt" - * The folder "subdir" should appear after the file "subdir.txt" - */ - di[path->size] = '/'; - di[path->size + 1] = '\0'; - } - - return di; -} - -static int alphasorted_dirent_cb(void *state, git_buf *full_path) -{ - char *entry; - git_vector *entry_names; - - entry_names = (git_vector *)state; - entry = alphasorted_dirent_info_new(full_path); - - if (entry == NULL) - return GIT_ENOMEM; - - if (git_vector_insert(entry_names, entry) < GIT_SUCCESS) { - git__free(entry); - return GIT_ENOMEM; - } - - return GIT_SUCCESS; -} -static int alphasorted_futils_direach( - git_buf *path, - int (*fn)(void *, git_buf *), - void *arg) -{ - char *entry; - git_vector entry_names; - unsigned int idx; - int error = GIT_SUCCESS; - git_buf entry_path = GIT_BUF_INIT; - - if (git_vector_init(&entry_names, 16, git__strcmp_cb) < GIT_SUCCESS) - return GIT_ENOMEM; - - error = git_path_direach(path, alphasorted_dirent_cb, &entry_names); - - git_vector_sort(&entry_names); - - for (idx = 0; idx < entry_names.length; ++idx) { - entry = (char *)git_vector_get(&entry_names, idx); - - /* We have to walk the entire vector even if there was an error, - * in order to free up memory, but we stop making callbacks after - * an error. - */ - if (error == GIT_SUCCESS) - error = git_buf_sets(&entry_path, entry); - - if (error == GIT_SUCCESS) { - ((struct status_st *)arg)->is_dir = - (entry[entry_path.size - 1] == '/'); - error = fn(arg, &entry_path); - } - - git__free(entry); - } - - git_buf_free(&entry_path); - git_vector_free(&entry_names); return error; } - int git_status_should_ignore(git_repository *repo, const char *path, int *ignored) { int error; git_ignores ignores; - if ((error = git_ignore__for_path(repo, path, &ignores)) == GIT_SUCCESS) - error = git_ignore__lookup(&ignores, path, ignored); + if (git_ignore__for_path(repo, path, &ignores) < 0) + return -1; + error = git_ignore__lookup(&ignores, path, ignored); git_ignore__free(&ignores); - return error; } diff --git a/src/strmap.h b/src/strmap.h new file mode 100644 index 000000000..55fbd7c6e --- /dev/null +++ b/src/strmap.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_strmap_h__ +#define INCLUDE_strmap_h__ + +#include "common.h" + +#define kmalloc git__malloc +#define kcalloc git__calloc +#define krealloc git__realloc +#define kfree git__free +#include "khash.h" + +__KHASH_TYPE(str, const char *, void *); +typedef khash_t(str) git_strmap; + +#define GIT__USE_STRMAP \ + __KHASH_IMPL(str, static inline, const char *, void *, 1, kh_str_hash_func, kh_str_hash_equal) + +#define git_strmap_alloc() kh_init(str) +#define git_strmap_free(h) kh_destroy(str, h), h = NULL +#define git_strmap_clear(h) kh_clear(str, h) + +#define git_strmap_num_entries(h) kh_size(h) + +#define git_strmap_lookup_index(h, k) kh_get(str, h, k) +#define git_strmap_valid_index(h, idx) (idx != kh_end(h)) + +#define git_strmap_exists(h, k) (kh_get(str, h, k) != kh_end(h)) + +#define git_strmap_value_at(h, idx) kh_val(h, idx) +#define git_strmap_set_value_at(h, idx, v) kh_val(h, idx) = v +#define git_strmap_delete_at(h, idx) kh_del(str, h, idx) + +#define git_strmap_insert(h, key, val, err) do { \ + khiter_t __pos = kh_put(str, h, key, &err); \ + if (err >= 0) kh_val(h, __pos) = val; \ + } while (0) + +#define git_strmap_insert2(h, key, val, old, err) do { \ + khiter_t __pos = kh_put(str, h, key, &err); \ + if (err >= 0) { \ + old = (err == 0) ? kh_val(h, __pos) : NULL; \ + kh_val(h, __pos) = val; \ + } } while (0) + +#define git_strmap_foreach kh_foreach +#define git_strmap_foreach_value kh_foreach_value + +#endif diff --git a/src/submodule.c b/src/submodule.c new file mode 100644 index 000000000..1b5b59f45 --- /dev/null +++ b/src/submodule.c @@ -0,0 +1,384 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "git2/config.h" +#include "git2/types.h" +#include "git2/repository.h" +#include "git2/index.h" +#include "git2/submodule.h" +#include "buffer.h" +#include "vector.h" +#include "posix.h" +#include "config_file.h" +#include "config.h" +#include "repository.h" + +static git_cvar_map _sm_update_map[] = { + {GIT_CVAR_STRING, "checkout", GIT_SUBMODULE_UPDATE_CHECKOUT}, + {GIT_CVAR_STRING, "rebase", GIT_SUBMODULE_UPDATE_REBASE}, + {GIT_CVAR_STRING, "merge", GIT_SUBMODULE_UPDATE_MERGE} +}; + +static git_cvar_map _sm_ignore_map[] = { + {GIT_CVAR_STRING, "all", GIT_SUBMODULE_IGNORE_ALL}, + {GIT_CVAR_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY}, + {GIT_CVAR_STRING, "untracked", GIT_SUBMODULE_IGNORE_UNTRACKED}, + {GIT_CVAR_STRING, "none", GIT_SUBMODULE_IGNORE_NONE} +}; + +static inline khint_t str_hash_no_trailing_slash(const char *s) +{ + khint_t h; + + for (h = 0; *s; ++s) + if (s[1] || *s != '/') + h = (h << 5) - h + *s; + + return h; +} + +static inline int str_equal_no_trailing_slash(const char *a, const char *b) +{ + size_t alen = a ? strlen(a) : 0; + size_t blen = b ? strlen(b) : 0; + + if (alen && a[alen] == '/') + alen--; + if (blen && b[blen] == '/') + blen--; + + return (alen == blen && strncmp(a, b, alen) == 0); +} + +__KHASH_IMPL(str, static inline, const char *, void *, 1, str_hash_no_trailing_slash, str_equal_no_trailing_slash); + +static git_submodule *submodule_alloc(const char *name) +{ + git_submodule *sm = git__calloc(1, sizeof(git_submodule)); + if (sm == NULL) + return sm; + + sm->path = sm->name = git__strdup(name); + if (!sm->name) { + git__free(sm); + return NULL; + } + + return sm; +} + +static void submodule_release(git_submodule *sm, int decr) +{ + if (!sm) + return; + + sm->refcount -= decr; + + if (sm->refcount == 0) { + if (sm->name != sm->path) + git__free(sm->path); + git__free(sm->name); + git__free(sm->url); + git__free(sm); + } +} + +static int submodule_from_entry( + git_strmap *smcfg, git_index_entry *entry) +{ + git_submodule *sm; + void *old_sm; + khiter_t pos; + int error; + + pos = git_strmap_lookup_index(smcfg, entry->path); + + if (git_strmap_valid_index(smcfg, pos)) + sm = git_strmap_value_at(smcfg, pos); + else + sm = submodule_alloc(entry->path); + + git_oid_cpy(&sm->oid, &entry->oid); + + if (strcmp(sm->path, entry->path) != 0) { + if (sm->path != sm->name) { + git__free(sm->path); + sm->path = sm->name; + } + sm->path = git__strdup(entry->path); + if (!sm->path) + goto fail; + } + + git_strmap_insert2(smcfg, sm->path, sm, old_sm, error); + if (error < 0) + goto fail; + sm->refcount++; + + if (old_sm && ((git_submodule *)old_sm) != sm) { + /* TODO: log warning about multiple entrys for same submodule path */ + submodule_release(old_sm, 1); + } + + return 0; + +fail: + submodule_release(sm, 0); + return -1; +} + +static int submodule_from_config( + const char *key, const char *value, void *data) +{ + git_strmap *smcfg = data; + const char *namestart; + const char *property; + git_buf name = GIT_BUF_INIT; + git_submodule *sm; + void *old_sm = NULL; + bool is_path; + khiter_t pos; + int error; + + if (git__prefixcmp(key, "submodule.") != 0) + return 0; + + namestart = key + strlen("submodule."); + property = strrchr(namestart, '.'); + if (property == NULL) + return 0; + property++; + is_path = (strcmp(property, "path") == 0); + + if (git_buf_set(&name, namestart, property - namestart - 1) < 0) + return -1; + + pos = git_strmap_lookup_index(smcfg, name.ptr); + if (!git_strmap_valid_index(smcfg, pos) && is_path) + pos = git_strmap_lookup_index(smcfg, value); + if (!git_strmap_valid_index(smcfg, pos)) + sm = submodule_alloc(name.ptr); + else + sm = git_strmap_value_at(smcfg, pos); + if (!sm) + goto fail; + + if (strcmp(sm->name, name.ptr) != 0) { + assert(sm->path == sm->name); + sm->name = git_buf_detach(&name); + + git_strmap_insert2(smcfg, sm->name, sm, old_sm, error); + if (error < 0) + goto fail; + sm->refcount++; + } + else if (is_path && strcmp(sm->path, value) != 0) { + assert(sm->path == sm->name); + sm->path = git__strdup(value); + if (sm->path == NULL) + goto fail; + + git_strmap_insert2(smcfg, sm->path, sm, old_sm, error); + if (error < 0) + goto fail; + sm->refcount++; + } + git_buf_free(&name); + + if (old_sm && ((git_submodule *)old_sm) != sm) { + /* TODO: log warning about multiple submodules with same path */ + submodule_release(old_sm, 1); + } + + if (is_path) + return 0; + + /* copy other properties into submodule entry */ + if (strcmp(property, "url") == 0) { + if (sm->url) { + git__free(sm->url); + sm->url = NULL; + } + if ((sm->url = git__strdup(value)) == NULL) + goto fail; + } + else if (strcmp(property, "update") == 0) { + int val; + if (git_config_lookup_map_value( + _sm_update_map, ARRAY_SIZE(_sm_update_map), value, &val) < 0) { + giterr_set(GITERR_INVALID, + "Invalid value for submodule update property: '%s'", value); + goto fail; + } + sm->update = (git_submodule_update_t)val; + } + else if (strcmp(property, "fetchRecurseSubmodules") == 0) { + if (git_config_parse_bool(&sm->fetch_recurse, value) < 0) + goto fail; + } + else if (strcmp(property, "ignore") == 0) { + int val; + if (git_config_lookup_map_value( + _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value, &val) < 0) { + giterr_set(GITERR_INVALID, + "Invalid value for submodule ignore property: '%s'", value); + goto fail; + } + sm->ignore = (git_submodule_ignore_t)val; + } + /* ignore other unknown submodule properties */ + + return 0; + +fail: + submodule_release(sm, 0); + git_buf_free(&name); + return -1; +} + +static int load_submodule_config(git_repository *repo) +{ + int error; + git_index *index; + unsigned int i, max_i; + git_oid gitmodules_oid; + git_strmap *smcfg; + struct git_config_file *mods = NULL; + + if (repo->submodules) + return 0; + + /* submodule data is kept in a hashtable with each submodule stored + * under both its name and its path. These are usually the same, but + * that is not guaranteed. + */ + smcfg = git_strmap_alloc(); + GITERR_CHECK_ALLOC(smcfg); + + /* scan index for gitmodules (and .gitmodules entry) */ + if ((error = git_repository_index__weakptr(&index, repo)) < 0) + goto cleanup; + memset(&gitmodules_oid, 0, sizeof(gitmodules_oid)); + max_i = git_index_entrycount(index); + + for (i = 0; i < max_i; i++) { + git_index_entry *entry = git_index_get(index, i); + if (S_ISGITLINK(entry->mode)) { + if ((error = submodule_from_entry(smcfg, entry)) < 0) + goto cleanup; + } + else if (strcmp(entry->path, ".gitmodules") == 0) + git_oid_cpy(&gitmodules_oid, &entry->oid); + } + + /* load .gitmodules from workdir if it exists */ + if (git_repository_workdir(repo) != NULL) { + /* look in workdir for .gitmodules */ + git_buf path = GIT_BUF_INIT; + if (!git_buf_joinpath( + &path, git_repository_workdir(repo), ".gitmodules") && + git_path_isfile(path.ptr)) + { + if (!(error = git_config_file__ondisk(&mods, path.ptr))) + error = git_config_file_open(mods); + } + git_buf_free(&path); + } + + /* load .gitmodules from object cache if not in workdir */ + if (!error && mods == NULL && !git_oid_iszero(&gitmodules_oid)) { + /* TODO: is it worth loading gitmodules from object cache? */ + } + + /* process .gitmodules info */ + if (!error && mods != NULL) + error = git_config_file_foreach(mods, submodule_from_config, smcfg); + + /* store submodule config in repo */ + if (!error) + repo->submodules = smcfg; + +cleanup: + if (mods != NULL) + git_config_file_free(mods); + if (error) + git_strmap_free(smcfg); + return error; +} + +void git_submodule_config_free(git_repository *repo) +{ + git_strmap *smcfg = repo->submodules; + git_submodule *sm; + + repo->submodules = NULL; + + if (smcfg == NULL) + return; + + git_strmap_foreach_value(smcfg, sm, { + submodule_release(sm,1); + }); + git_strmap_free(smcfg); +} + +static int submodule_cmp(const void *a, const void *b) +{ + return strcmp(((git_submodule *)a)->name, ((git_submodule *)b)->name); +} + +int git_submodule_foreach( + git_repository *repo, + int (*callback)(const char *name, void *payload), + void *payload) +{ + int error; + git_submodule *sm; + git_vector seen = GIT_VECTOR_INIT; + seen._cmp = submodule_cmp; + + if ((error = load_submodule_config(repo)) < 0) + return error; + + git_strmap_foreach_value(repo->submodules, sm, { + /* usually the following will not come into play */ + if (sm->refcount > 1) { + if (git_vector_bsearch(&seen, sm) != GIT_ENOTFOUND) + continue; + if ((error = git_vector_insert(&seen, sm)) < 0) + break; + } + + if ((error = callback(sm->name, payload)) < 0) + break; + }); + + git_vector_free(&seen); + + return error; +} + +int git_submodule_lookup( + git_submodule **sm_ptr, /* NULL allowed if user only wants to test */ + git_repository *repo, + const char *name) /* trailing slash is allowed */ +{ + khiter_t pos; + + if (load_submodule_config(repo) < 0) + return -1; + + pos = git_strmap_lookup_index(repo->submodules, name); + if (!git_strmap_valid_index(repo->submodules, pos)) + return GIT_ENOTFOUND; + + if (sm_ptr) + *sm_ptr = git_strmap_value_at(repo->submodules, pos); + + return 0; +} @@ -61,26 +61,32 @@ const char *git_tag_message(git_tag *t) return t->message; } +static int tag_error(const char *str) +{ + giterr_set(GITERR_TAG, "Failed to parse tag. %s", str); + return -1; +} + int git_tag__parse_buffer(git_tag *tag, const char *buffer, size_t length) { static const char *tag_types[] = { NULL, "commit\n", "tree\n", "blob\n", "tag\n" }; - unsigned int i, text_len; + unsigned int i; + size_t text_len; char *search; - int error; const char *buffer_end = buffer + length; - if ((error = git_oid__parse(&tag->target, &buffer, buffer_end, "object ")) < 0) - return git__rethrow(error, "Failed to parse tag. Object field invalid"); + if (git_oid__parse(&tag->target, &buffer, buffer_end, "object ") < 0) + return tag_error("Object field invalid"); if (buffer + 5 >= buffer_end) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Object too short"); + return tag_error("Object too short"); if (memcmp(buffer, "type ", 5) != 0) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Type field not found"); + return tag_error("Type field not found"); buffer += 5; tag->type = GIT_OBJ_BAD; @@ -89,7 +95,7 @@ int git_tag__parse_buffer(git_tag *tag, const char *buffer, size_t length) size_t type_length = strlen(tag_types[i]); if (buffer + type_length >= buffer_end) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Object too short"); + return tag_error("Object too short"); if (memcmp(buffer, tag_types[i], type_length) == 0) { tag->type = i; @@ -99,25 +105,24 @@ int git_tag__parse_buffer(git_tag *tag, const char *buffer, size_t length) } if (tag->type == GIT_OBJ_BAD) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Invalid object type"); + return tag_error("Invalid object type"); if (buffer + 4 >= buffer_end) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Object too short"); + return tag_error("Object too short"); if (memcmp(buffer, "tag ", 4) != 0) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Tag field not found"); + return tag_error("Tag field not found"); buffer += 4; search = memchr(buffer, '\n', buffer_end - buffer); if (search == NULL) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. Object too short"); + return tag_error("Object too short"); text_len = search - buffer; tag->tag_name = git__malloc(text_len + 1); - if (tag->tag_name == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(tag->tag_name); memcpy(tag->tag_name, buffer, text_len); tag->tag_name[text_len] = '\0'; @@ -127,27 +132,24 @@ int git_tag__parse_buffer(git_tag *tag, const char *buffer, size_t length) tag->tagger = NULL; if (*buffer != '\n') { tag->tagger = git__malloc(sizeof(git_signature)); - if (tag->tagger == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(tag->tagger); - if ((error = git_signature__parse(tag->tagger, &buffer, buffer_end, "tagger ", '\n') != 0)) { - return git__rethrow(error, "Failed to parse tag"); - } + if (git_signature__parse(tag->tagger, &buffer, buffer_end, "tagger ", '\n') < 0) + return -1; } if( *buffer != '\n' ) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tag. No new line before message"); + return tag_error("No new line before message"); text_len = buffer_end - ++buffer; tag->message = git__malloc(text_len + 1); - if (tag->message == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(tag->message); memcpy(tag->message, buffer, text_len); tag->message[text_len] = '\0'; - return GIT_SUCCESS; + return 0; } static int retrieve_tag_reference( @@ -161,17 +163,28 @@ static int retrieve_tag_reference( *tag_reference_out = NULL; - error = git_buf_joinpath(ref_name_out, GIT_REFS_TAGS_DIR, tag_name); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to retrieve tag reference"); + if (git_buf_joinpath(ref_name_out, GIT_REFS_TAGS_DIR, tag_name) < 0) + return -1; error = git_reference_lookup(&tag_ref, repo, ref_name_out->ptr); if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to retrieve tag reference"); + return error; /* Be it not foundo or corrupted */ *tag_reference_out = tag_ref; - return GIT_SUCCESS; + return 0; +} + +static int retrieve_tag_reference_oid( + git_oid *oid, + git_buf *ref_name_out, + git_repository *repo, + const char *tag_name) +{ + if (git_buf_joinpath(ref_name_out, GIT_REFS_TAGS_DIR, tag_name) < 0) + return -1; + + return git_reference_name_to_oid(oid, repo, ref_name_out->ptr); } static int write_tag_annotation( @@ -182,7 +195,6 @@ static int write_tag_annotation( const git_signature *tagger, const char *message) { - int error = GIT_SUCCESS; git_buf tag = GIT_BUF_INIT; git_odb *odb; @@ -193,25 +205,20 @@ static int write_tag_annotation( git_buf_putc(&tag, '\n'); git_buf_puts(&tag, message); - error = git_buf_lasterror(&tag); - if (error < GIT_SUCCESS) { - git_buf_free(&tag); - return git__rethrow(error, "Not enough memory to build the tag data"); - } - - error = git_repository_odb__weakptr(&odb, repo); - if (error < GIT_SUCCESS) { - git_buf_free(&tag); - return error; - } + if (git_buf_oom(&tag)) + goto on_error; - error = git_odb_write(oid, odb, tag.ptr, tag.size, GIT_OBJ_TAG); - git_buf_free(&tag); + if (git_repository_odb__weakptr(&odb, repo) < 0) + goto on_error; - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to create tag annotation"); + if (git_odb_write(oid, odb, tag.ptr, tag.size, GIT_OBJ_TAG) < 0) + goto on_error; - return error; + git_buf_free(&tag); + return 0; +on_error: + git_buf_free(&tag); + return -1; } static int git_tag_create__internal( @@ -227,51 +234,38 @@ static int git_tag_create__internal( git_reference *new_ref = NULL; git_buf ref_name = GIT_BUF_INIT; - int error, should_update_ref = 0; - const char *errmsg = "Failed to create tag"; + int error; assert(repo && tag_name && target); assert(!create_tag_annotation || (tagger && message)); - if (git_object_owner(target) != repo) - return git__throw(GIT_EINVALIDARGS, - "The given target does not belong to this repository"); + if (git_object_owner(target) != repo) { + giterr_set(GITERR_INVALID, "The given target does not belong to this repository"); + return -1; + } - error = retrieve_tag_reference(&new_ref, &ref_name, repo, tag_name); + error = retrieve_tag_reference_oid(oid, &ref_name, repo, tag_name); if (error < GIT_SUCCESS && error != GIT_ENOTFOUND) - goto cleanup; + return -1; /** Ensure the tag name doesn't conflict with an already existing * reference unless overwriting has explictly been requested **/ - if (new_ref != NULL) { - if (!allow_ref_overwrite) { - git_oid_cpy(oid, git_reference_oid(new_ref)); - error = GIT_EEXISTS; - errmsg = "Tag already exists"; - goto cleanup; - } else { - should_update_ref = 1; - } + if (error == 0 && !allow_ref_overwrite) { + git_buf_free(&ref_name); + giterr_set(GITERR_TAG, "Tag already exists"); + return GIT_EEXISTS; } if (create_tag_annotation) { - if ((error = write_tag_annotation(oid, repo, tag_name, target, tagger, message)) < GIT_SUCCESS) - goto cleanup; + if (write_tag_annotation(oid, repo, tag_name, target, tagger, message) < 0) + return -1; } else git_oid_cpy(oid, git_object_id(target)); - if (!should_update_ref) - error = git_reference_create_oid(&new_ref, repo, ref_name.ptr, oid, 0); - else - error = git_reference_set_oid(new_ref, oid); + error = git_reference_create_oid(&new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite); -cleanup: git_reference_free(new_ref); git_buf_free(&ref_name); - - if (error < GIT_SUCCESS) - git__rethrow(error, "%s", errmsg); - return error; } @@ -300,8 +294,7 @@ int git_tag_create_lightweight( int git_tag_create_frombuffer(git_oid *oid, git_repository *repo, const char *buffer, int allow_ref_overwrite) { git_tag tag; - int error, should_update_ref = 0; - const char *errmsg = "Failed to create tag"; + int error; git_odb *odb; git_odb_stream *stream; git_odb_object *target_obj; @@ -313,71 +306,66 @@ int git_tag_create_frombuffer(git_oid *oid, git_repository *repo, const char *bu memset(&tag, 0, sizeof(tag)); - error = git_repository_odb__weakptr(&odb, repo); - if (error < GIT_SUCCESS) - return error; + if (git_repository_odb__weakptr(&odb, repo) < 0) + return -1; /* validate the buffer */ - if ((error = git_tag__parse_buffer(&tag, buffer, strlen(buffer))) < GIT_SUCCESS) - goto cleanup; + if (git_tag__parse_buffer(&tag, buffer, strlen(buffer)) < 0) + return -1; /* validate the target */ - if ((error = git_odb_read(&target_obj, odb, &tag.target)) < GIT_SUCCESS) - goto cleanup; + if (git_odb_read(&target_obj, odb, &tag.target) < 0) + goto on_error; if (tag.type != target_obj->raw.type) { - error = GIT_EINVALIDTYPE; - errmsg = "The type for the given target is invalid"; - goto cleanup; + giterr_set(GITERR_TAG, "The type for the given target is invalid"); + goto on_error; } - git_odb_object_free(target_obj); - - error = retrieve_tag_reference(&new_ref, &ref_name, repo, tag.tag_name); + error = retrieve_tag_reference_oid(oid, &ref_name, repo, tag.tag_name); if (error < GIT_SUCCESS && error != GIT_ENOTFOUND) - goto cleanup; + goto on_error; + + /* We don't need these objects after this */ + git_signature_free(tag.tagger); + git__free(tag.tag_name); + git__free(tag.message); + git_odb_object_free(target_obj); /** Ensure the tag name doesn't conflict with an already existing * reference unless overwriting has explictly been requested **/ - if (new_ref != NULL) { - if (!allow_ref_overwrite) { - git_oid_cpy(oid, git_reference_oid(new_ref)); - error = GIT_EEXISTS; - errmsg = "Tag already exists"; - goto cleanup; - } else { - should_update_ref = 1; - } + if (error == 0 && !allow_ref_overwrite) { + giterr_set(GITERR_TAG, "Tag already exists"); + return GIT_EEXISTS; } /* write the buffer */ - if ((error = git_odb_open_wstream(&stream, odb, strlen(buffer), GIT_OBJ_TAG)) < GIT_SUCCESS) - goto cleanup; + if (git_odb_open_wstream(&stream, odb, strlen(buffer), GIT_OBJ_TAG) < 0) + return -1; stream->write(stream, buffer, strlen(buffer)); error = stream->finalize_write(oid, stream); stream->free(stream); - if (error < GIT_SUCCESS) - goto cleanup; + if (error < 0) { + git_buf_free(&ref_name); + return -1; + } - if (!should_update_ref) - error = git_reference_create_oid(&new_ref, repo, ref_name.ptr, oid, 0); - else - error = git_reference_set_oid(new_ref, oid); + error = git_reference_create_oid(&new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite); -cleanup: git_reference_free(new_ref); - git_signature_free(tag.tagger); - git__free(tag.tag_name); - git__free(tag.message); git_buf_free(&ref_name); - if (error < GIT_SUCCESS) - git__rethrow(error, "%s", errmsg); - return error; + +on_error: + git_signature_free(tag.tagger); + git__free(tag.tag_name); + git__free(tag.message); + git_odb_object_free(target_obj); + return -1; } int git_tag_delete(git_repository *repo, const char *tag_name) @@ -390,8 +378,8 @@ int git_tag_delete(git_repository *repo, const char *tag_name) git_buf_free(&ref_name); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to delete tag"); + if (error < 0) + return -1; return git_reference_delete(tag_ref); } @@ -414,13 +402,13 @@ static int tag_list_cb(const char *tag_name, void *payload) tag_filter_data *filter; if (git__prefixcmp(tag_name, GIT_REFS_TAGS_DIR) != 0) - return GIT_SUCCESS; + return 0; filter = (tag_filter_data *)payload; if (!*filter->pattern || p_fnmatch(filter->pattern, tag_name + GIT_REFS_TAGS_DIR_LEN, 0) == GIT_SUCCESS) return git_vector_insert(filter->taglist, git__strdup(tag_name)); - return GIT_SUCCESS; + return 0; } int git_tag_list_match(git_strarray *tag_names, const char *pattern, git_repository *repo) @@ -438,17 +426,37 @@ int git_tag_list_match(git_strarray *tag_names, const char *pattern, git_reposit filter.pattern = pattern; error = git_reference_foreach(repo, GIT_REF_OID|GIT_REF_PACKED, &tag_list_cb, (void *)&filter); - if (error < GIT_SUCCESS) { + if (error < 0) { git_vector_free(&taglist); - return git__rethrow(error, "Failed to list tags"); + return -1; } tag_names->strings = (char **)taglist.contents; tag_names->count = taglist.length; - return GIT_SUCCESS; + return 0; } int git_tag_list(git_strarray *tag_names, git_repository *repo) { return git_tag_list_match(tag_names, "", repo); } + +int git_tag_peel(git_object **tag_target, git_tag *tag) +{ + int error; + git_object *target; + + assert(tag_target && tag); + + if (git_tag_target(&target, tag) < 0) + return -1; + + if (git_object_type(target) == GIT_OBJ_TAG) { + error = git_tag_peel(tag_target, (git_tag *)target); + git_object_free(target); + return error; + } + + *tag_target = target; + return 0; +} diff --git a/src/transport.c b/src/transport.c index 4910f2433..0c88e44d3 100644 --- a/src/transport.c +++ b/src/transport.c @@ -54,7 +54,8 @@ static git_transport_cb transport_find_fn(const char *url) int git_transport_dummy(git_transport **transport) { GIT_UNUSED(transport); - return git__throw(GIT_ENOTIMPLEMENTED, "This protocol isn't implemented. Sorry"); + giterr_set(GITERR_NET, "This transport isn't implemented. Sorry"); + return -1; } int git_transport_new(git_transport **out, const char *url) @@ -70,11 +71,10 @@ int git_transport_new(git_transport **out, const char *url) error = fn(&transport); if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to create new transport"); + return error; transport->url = git__strdup(url); - if (transport->url == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(transport->url); *out = transport; diff --git a/src/transport.h b/src/transport.h index 63dd7dab6..125df2745 100644 --- a/src/transport.h +++ b/src/transport.h @@ -8,6 +8,7 @@ #define INCLUDE_transport_h__ #include "git2/net.h" +#include "git2/indexer.h" #include "vector.h" #define GIT_CAP_OFS_DELTA "ofs-delta" @@ -66,22 +67,14 @@ struct git_transport { */ int (*push)(struct git_transport *transport); /** - * Send a 'done' message - */ - int (*send_done)(struct git_transport *transport); - /** * Negotiate the minimal amount of objects that need to be * retrieved */ int (*negotiate_fetch)(struct git_transport *transport, git_repository *repo, const git_vector *wants); /** - * Send a flush - */ - int (*send_flush)(struct git_transport *transport); - /** * Download the packfile */ - int (*download_pack)(char **out, struct git_transport *transport, git_repository *repo); + int (*download_pack)(struct git_transport *transport, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats); /** * Fetch the changes */ diff --git a/src/transports/git.c b/src/transports/git.c index 88e7e8160..31bc21c96 100644 --- a/src/transports/git.c +++ b/src/transports/git.c @@ -46,11 +46,13 @@ static int gen_proto(git_buf *request, const char *cmd, const char *url) char *delim, *repo; char default_command[] = "git-upload-pack"; char host[] = "host="; - int len; + size_t len; delim = strchr(url, '/'); - if (delim == NULL) - return git__throw(GIT_EOBJCORRUPTED, "Failed to create proto-request: malformed URL"); + if (delim == NULL) { + giterr_set(GITERR_NET, "Malformed URL"); + return -1; + } repo = delim; @@ -64,11 +66,15 @@ static int gen_proto(git_buf *request, const char *cmd, const char *url) len = 4 + strlen(cmd) + 1 + strlen(repo) + 1 + strlen(host) + (delim - url) + 1; git_buf_grow(request, len); - git_buf_printf(request, "%04x%s %s%c%s", len, cmd, repo, 0, host); + git_buf_printf(request, "%04x%s %s%c%s", + (unsigned int)(len & 0x0FFFF), cmd, repo, 0, host); git_buf_put(request, url, delim - url); git_buf_putc(request, '\0'); - return git_buf_lasterror(request); + if (git_buf_oom(request)) + return GIT_ENOMEM; + + return 0; } static int send_request(GIT_SOCKET s, const char *cmd, const char *url) @@ -77,7 +83,7 @@ static int send_request(GIT_SOCKET s, const char *cmd, const char *url) git_buf request = GIT_BUF_INIT; error = gen_proto(&request, cmd, url); - if (error < GIT_SUCCESS) + if (error < 0) goto cleanup; error = gitno_send(s, request.ptr, request.size, 0); @@ -102,9 +108,8 @@ static int do_connect(transport_git *t, const char *url) if (!git__prefixcmp(url, prefix)) url += strlen(prefix); - error = gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT); - if (error < GIT_SUCCESS) - return error; + if (gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT) < 0) + return -1; s = gitno_connect(host, port); connected = 1; @@ -115,9 +120,11 @@ static int do_connect(transport_git *t, const char *url) git__free(port); if (error < GIT_SUCCESS && s > 0) - close(s); - if (!connected) - error = git__throw(GIT_EOSERR, "Failed to connect to any of the addresses"); + gitno_close(s); + if (!connected) { + giterr_set(GITERR_NET, "Failed to connect to the host"); + return -1; + } return error; } @@ -128,33 +135,30 @@ static int do_connect(transport_git *t, const char *url) static int store_refs(transport_git *t) { gitno_buffer *buf = &t->buf; - int error = GIT_SUCCESS; + int ret = 0; while (1) { - error = gitno_recv(buf); - if (error < GIT_SUCCESS) - return git__rethrow(GIT_EOSERR, "Failed to receive data"); - if (error == GIT_SUCCESS) /* Orderly shutdown, so exit */ - return GIT_SUCCESS; - - error = git_protocol_store_refs(&t->proto, buf->data, buf->offset); - if (error == GIT_ESHORTBUFFER) { + if ((ret = gitno_recv(buf)) < 0) + return -1; + if (ret == 0) /* Orderly shutdown, so exit */ + return 0; + + ret = git_protocol_store_refs(&t->proto, buf->data, buf->offset); + if (ret == GIT_ESHORTBUFFER) { gitno_consume_n(buf, buf->len); continue; } - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to store refs"); + if (ret < 0) + return ret; gitno_consume_n(buf, buf->offset); if (t->proto.flush) { /* No more refs */ t->proto.flush = 0; - return GIT_SUCCESS; + return 0; } } - - return error; } static int detect_caps(transport_git *t) @@ -167,7 +171,7 @@ static int detect_caps(transport_git *t) pkt = git_vector_get(refs, 0); /* No refs or capabilites, odd but not a problem */ if (pkt == NULL || pkt->capabilities == NULL) - return GIT_SUCCESS; + return 0; ptr = pkt->capabilities; while (ptr != NULL && *ptr != '\0') { @@ -184,7 +188,7 @@ static int detect_caps(transport_git *t) ptr = strchr(ptr, ' '); } - return GIT_SUCCESS; + return 0; } /* @@ -194,36 +198,33 @@ static int detect_caps(transport_git *t) static int git_connect(git_transport *transport, int direction) { transport_git *t = (transport_git *) transport; - int error = GIT_SUCCESS; - if (direction == GIT_DIR_PUSH) - return git__throw(GIT_EINVALIDARGS, "Pushing is not supported with the git protocol"); + if (direction == GIT_DIR_PUSH) { + giterr_set(GITERR_NET, "Pushing over git:// is not supported"); + return -1; + } t->parent.direction = direction; - error = git_vector_init(&t->refs, 16, NULL); - if (error < GIT_SUCCESS) - goto cleanup; + if (git_vector_init(&t->refs, 16, NULL) < 0) + return -1; /* Connect and ask for the refs */ - error = do_connect(t, transport->url); - if (error < GIT_SUCCESS) - return error; + if (do_connect(t, transport->url) < 0) + goto cleanup; gitno_buffer_setup(&t->buf, t->buff, sizeof(t->buff), t->socket); t->parent.connected = 1; - error = store_refs(t); - if (error < GIT_SUCCESS) - return error; + if (store_refs(t) < 0) + goto cleanup; - error = detect_caps(t); + if (detect_caps(t) < 0) + goto cleanup; + return 0; cleanup: - if (error < GIT_SUCCESS) { - git_vector_free(&t->refs); - } - - return error; + git_vector_free(&t->refs); + return -1; } static int git_ls(git_transport *transport, git_headlist_cb list_cb, void *opaque) @@ -241,149 +242,129 @@ static int git_ls(git_transport *transport, git_headlist_cb list_cb, void *opaqu pkt = (git_pkt_ref *)p; - if (list_cb(&pkt->head, opaque) < 0) - return git__throw(GIT_ERROR, - "The user callback returned an error code"); + if (list_cb(&pkt->head, opaque) < 0) { + giterr_set(GITERR_NET, "User callback returned error"); + return -1; + } } - return GIT_SUCCESS; + return 0; +} + +/* Wait until we get an ack from the */ +static int recv_pkt(gitno_buffer *buf) +{ + const char *ptr = buf->data, *line_end; + git_pkt *pkt; + int pkt_type, error; + + do { + /* Wait for max. 1 second */ + if ((error = gitno_select_in(buf, 1, 0)) < 0) { + return -1; + } else if (error == 0) { + /* + * Some servers don't respond immediately, so if this + * happens, we keep sending information until it + * answers. Pretend we received a NAK to convince higher + * layers to do so. + */ + return GIT_PKT_NAK; + } + + if ((error = gitno_recv(buf)) < 0) + return -1; + + error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset); + if (error == GIT_ESHORTBUFFER) + continue; + if (error < 0) + return -1; + } while (error); + + gitno_consume(buf, line_end); + pkt_type = pkt->type; + git__free(pkt); + + return pkt_type; } static int git_negotiate_fetch(git_transport *transport, git_repository *repo, const git_vector *wants) { transport_git *t = (transport_git *) transport; git_revwalk *walk; - git_reference *ref; - git_strarray refs; git_oid oid; int error; unsigned int i; + git_buf data = GIT_BUF_INIT; gitno_buffer *buf = &t->buf; - error = git_pkt_send_wants(wants, &t->caps, t->socket); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to send wants list"); - - error = git_reference_listall(&refs, repo, GIT_REF_LISTALL); - if (error < GIT_ERROR) - return git__rethrow(error, "Failed to list all references"); - - error = git_revwalk_new(&walk, repo); - if (error < GIT_ERROR) { - error = git__rethrow(error, "Failed to list all references"); - goto cleanup; - } - git_revwalk_sorting(walk, GIT_SORT_TIME); + if (git_pkt_buffer_wants(wants, &t->caps, &data) < 0) + return -1; - for (i = 0; i < refs.count; ++i) { - /* No tags */ - if (!git__prefixcmp(refs.strings[i], GIT_REFS_TAGS_DIR)) - continue; + if (git_fetch_setup_walk(&walk, repo) < 0) + goto on_error; - error = git_reference_lookup(&ref, repo, refs.strings[i]); - if (error < GIT_ERROR) { - error = git__rethrow(error, "Failed to lookup %s", refs.strings[i]); - goto cleanup; - } - - if (git_reference_type(ref) == GIT_REF_SYMBOLIC) - continue; - - error = git_revwalk_push(walk, git_reference_oid(ref)); - if (error < GIT_ERROR) { - error = git__rethrow(error, "Failed to push %s", refs.strings[i]); - goto cleanup; - } - } - git_strarray_free(&refs); + if (gitno_send(t->socket, data.ptr, data.size, 0) < 0) + goto on_error; + git_buf_clear(&data); /* * We don't support any kind of ACK extensions, so the negotiation * boils down to sending what we have and listening for an ACK * every once in a while. */ i = 0; - while ((error = git_revwalk_next(&oid, walk)) == GIT_SUCCESS) { - error = git_pkt_send_have(&oid, t->socket); + while ((error = git_revwalk_next(&oid, walk)) == 0) { + git_pkt_buffer_have(&oid, &data); i++; if (i % 20 == 0) { - const char *ptr = buf->data, *line_end; - git_pkt *pkt; - git_pkt_send_flush(t->socket); - while (1) { - /* Wait for max. 1 second */ - error = gitno_select_in(buf, 1, 0); - if (error < GIT_SUCCESS) { - error = git__throw(GIT_EOSERR, "Error in select"); - } else if (error == 0) { - /* - * Some servers don't respond immediately, so if this - * happens, we keep sending information until it - * answers. - */ - break; - } - - error = gitno_recv(buf); - if (error < GIT_SUCCESS) { - error = git__rethrow(error, "Error receiving data"); - goto cleanup; - } - error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset); - if (error == GIT_ESHORTBUFFER) - continue; - if (error < GIT_SUCCESS) { - error = git__rethrow(error, "Failed to get answer"); - goto cleanup; - } - - gitno_consume(buf, line_end); - - if (pkt->type == GIT_PKT_ACK) { - git__free(pkt); - error = GIT_SUCCESS; - goto done; - } else if (pkt->type == GIT_PKT_NAK) { - git__free(pkt); - break; - } else { - error = git__throw(GIT_ERROR, "Got unexpected pkt type"); - goto cleanup; - } - } - } - } - if (error == GIT_EREVWALKOVER) - error = GIT_SUCCESS; + int pkt_type; -done: - git_pkt_send_flush(t->socket); - git_pkt_send_done(t->socket); + git_pkt_buffer_flush(&data); + if (git_buf_oom(&data)) + goto on_error; -cleanup: - git_revwalk_free(walk); + if (gitno_send(t->socket, data.ptr, data.size, 0) < 0) + goto on_error; - return error; -} + pkt_type = recv_pkt(buf); -static int git_send_flush(git_transport *transport) -{ - transport_git *t = (transport_git *) transport; + if (pkt_type == GIT_PKT_ACK) { + break; + } else if (pkt_type == GIT_PKT_NAK) { + continue; + } else { + giterr_set(GITERR_NET, "Unexpected pkt type"); + goto on_error; + } - return git_pkt_send_flush(t->socket); -} + } + } + if (error < 0 && error != GIT_EREVWALKOVER) + goto on_error; -static int git_send_done(git_transport *transport) -{ - transport_git *t = (transport_git *) transport; + /* Tell the other end that we're done negotiating */ + git_buf_clear(&data); + git_pkt_buffer_flush(&data); + git_pkt_buffer_done(&data); + if (gitno_send(t->socket, data.ptr, data.size, 0) < 0) + goto on_error; - return git_pkt_send_done(t->socket); + git_buf_free(&data); + git_revwalk_free(walk); + return 0; + +on_error: + git_buf_free(&data); + git_revwalk_free(walk); + return -1; } -static int git_download_pack(char **out, git_transport *transport, git_repository *repo) +static int git_download_pack(git_transport *transport, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats) { transport_git *t = (transport_git *) transport; - int error = GIT_SUCCESS; + int error = 0, read_bytes; gitno_buffer *buf = &t->buf; git_pkt *pkt; const char *line_end, *ptr; @@ -391,7 +372,7 @@ static int git_download_pack(char **out, git_transport *transport, git_repositor /* * For now, we ignore everything and wait for the pack */ - while (1) { + do { ptr = buf->data; /* Whilst we're searching for the pack */ while (1) { @@ -408,7 +389,7 @@ static int git_download_pack(char **out, git_transport *transport, git_repositor if (pkt->type == GIT_PKT_PACK) { git__free(pkt); - return git_fetch__download_pack(out, buf->data, buf->offset, t->socket, repo); + return git_fetch__download_pack(buf->data, buf->offset, t->socket, repo, bytes, stats); } /* For now we don't care about anything */ @@ -416,34 +397,28 @@ static int git_download_pack(char **out, git_transport *transport, git_repositor gitno_consume(buf, line_end); } - error = gitno_recv(buf); - if (error < GIT_SUCCESS) - return git__rethrow(GIT_EOSERR, "Failed to receive data"); - if (error == 0) { /* Orderly shutdown */ - return GIT_SUCCESS; - } + read_bytes = gitno_recv(buf); + } while (read_bytes); - } + return read_bytes; } - static int git_close(git_transport *transport) { transport_git *t = (transport_git*) transport; - int error; /* Can't do anything if there's an error, so don't bother checking */ git_pkt_send_flush(t->socket); - error = gitno_close(t->socket); - - if (error < 0) - error = git__throw(GIT_EOSERR, "Failed to close socket"); + if (gitno_close(t->socket) < 0) { + giterr_set(GITERR_NET, "Failed to close socket"); + return -1; + } #ifdef GIT_WIN32 WSACleanup(); #endif - return error; + return 0; } static void git_free(git_transport *transport) @@ -472,16 +447,13 @@ int git_transport_git(git_transport **out) #endif t = git__malloc(sizeof(transport_git)); - if (t == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(t); memset(t, 0x0, sizeof(transport_git)); t->parent.connect = git_connect; t->parent.ls = git_ls; t->parent.negotiate_fetch = git_negotiate_fetch; - t->parent.send_flush = git_send_flush; - t->parent.send_done = git_send_done; t->parent.download_pack = git_download_pack; t->parent.close = git_close; t->parent.free = git_free; @@ -494,9 +466,10 @@ int git_transport_git(git_transport **out) ret = WSAStartup(MAKEWORD(2,2), &t->wsd); if (ret != 0) { git_free(*out); - return git__throw(GIT_EOSERR, "Winsock init failed"); + giterr_set(GITERR_NET, "Winsock init failed"); + return -1; } #endif - return GIT_SUCCESS; + return 0; } diff --git a/src/transports/http.c b/src/transports/http.c index 2842d08fd..3690f3ded 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -32,7 +32,7 @@ typedef struct { git_protocol proto; git_vector refs; git_vector common; - int socket; + GIT_SOCKET socket; git_buf buf; git_remote_head **heads; int error; @@ -77,7 +77,10 @@ static int gen_request(git_buf *buf, const char *url, const char *host, const ch } git_buf_puts(buf, "\r\n"); - return git_buf_lasterror(buf); + if (git_buf_oom(buf)) + return GIT_ENOMEM; + + return 0; } static int do_connect(transport_http *t, const char *host, const char *port) @@ -85,16 +88,15 @@ static int do_connect(transport_http *t, const char *host, const char *port) GIT_SOCKET s = -1; if (t->parent.connected && http_should_keep_alive(&t->parser)) - return GIT_SUCCESS; + return 0; + + if ((s = gitno_connect(host, port)) < 0) + return -1; - s = gitno_connect(host, port); - if (s < GIT_SUCCESS) { - return git__rethrow(s, "Failed to connect to host"); - } t->socket = s; t->parent.connected = 1; - return GIT_SUCCESS; + return 0; } /* @@ -116,8 +118,7 @@ static int on_header_field(http_parser *parser, const char *str, size_t len) t->ct_finished = 1; t->ct_found = 0; t->content_type = git__strdup(git_buf_cstr(buf)); - if (t->content_type == NULL) - return t->error = GIT_ENOMEM; + GITERR_CHECK_ALLOC(t->content_type); git_buf_clear(buf); } @@ -164,6 +165,12 @@ static int on_headers_complete(http_parser *parser) transport_http *t = (transport_http *) parser->data; git_buf *buf = &t->buf; + /* The content-type is text/plain for 404, so don't validate */ + if (parser->status_code == 404) { + git_buf_clear(buf); + return 0; + } + if (t->content_type == NULL) { t->content_type = git__strdup(git_buf_cstr(buf)); if (t->content_type == NULL) @@ -173,10 +180,10 @@ static int on_headers_complete(http_parser *parser) git_buf_clear(buf); git_buf_printf(buf, "application/x-git-%s-advertisement", t->service); if (git_buf_oom(buf)) - return GIT_ENOMEM; + return t->error = GIT_ENOMEM; if (strcmp(t->content_type, git_buf_cstr(buf))) - return t->error = git__throw(GIT_EOBJCORRUPTED, "Content-Type '%s' is wrong", t->content_type); + return t->error = -1; git_buf_clear(buf); return 0; @@ -186,6 +193,10 @@ static int on_body_store_refs(http_parser *parser, const char *str, size_t len) { transport_http *t = (transport_http *) parser->data; + if (parser->status_code == 404) { + return git_buf_put(&t->buf, str, len); + } + return git_protocol_store_refs(&t->proto, str, len); } @@ -194,16 +205,22 @@ static int on_message_complete(http_parser *parser) transport_http *t = (transport_http *) parser->data; t->transfer_finished = 1; + + if (parser->status_code == 404) { + giterr_set(GITERR_NET, "Remote error: %s", git_buf_cstr(&t->buf)); + t->error = -1; + } + return 0; } static int store_refs(transport_http *t) { - int error = GIT_SUCCESS; http_parser_settings settings; char buffer[1024]; gitno_buffer buf; git_pkt *pkt; + int ret; http_parser_init(&t->parser, HTTP_RESPONSE); t->parser.data = t; @@ -219,83 +236,76 @@ static int store_refs(transport_http *t) while(1) { size_t parsed; - error = gitno_recv(&buf); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Error receiving data from network"); + if ((ret = gitno_recv(&buf)) < 0) + return -1; parsed = http_parser_execute(&t->parser, &settings, buf.data, buf.offset); /* Both should happen at the same time */ - if (parsed != buf.offset || t->error < GIT_SUCCESS) - return git__rethrow(t->error, "Error parsing HTTP data"); + if (parsed != buf.offset || t->error < 0) + return t->error; gitno_consume_n(&buf, parsed); - if (error == 0 || t->transfer_finished) - return GIT_SUCCESS; + if (ret == 0 || t->transfer_finished) + return 0; } pkt = git_vector_get(&t->refs, 0); - if (pkt == NULL || pkt->type != GIT_PKT_COMMENT) - return t->error = git__throw(GIT_EOBJCORRUPTED, "Not a valid smart HTTP response"); - else + if (pkt == NULL || pkt->type != GIT_PKT_COMMENT) { + giterr_set(GITERR_NET, "Invalid HTTP response"); + return t->error = -1; + } else { git_vector_remove(&t->refs, 0); + } - return error; + return 0; } static int http_connect(git_transport *transport, int direction) { transport_http *t = (transport_http *) transport; - int error; + int ret; git_buf request = GIT_BUF_INIT; const char *service = "upload-pack"; const char *url = t->parent.url, *prefix = "http://"; - if (direction == GIT_DIR_PUSH) - return git__throw(GIT_EINVALIDARGS, "Pushing over HTTP is not supported"); + if (direction == GIT_DIR_PUSH) { + giterr_set(GITERR_NET, "Pushing over HTTP is not implemented"); + return -1; + } t->parent.direction = direction; - error = git_vector_init(&t->refs, 16, NULL); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to init refs vector"); + if (git_vector_init(&t->refs, 16, NULL) < 0) + return -1; if (!git__prefixcmp(url, prefix)) url += strlen(prefix); - error = gitno_extract_host_and_port(&t->host, &t->port, url, "80"); - if (error < GIT_SUCCESS) + if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, "80")) < 0) goto cleanup; t->service = git__strdup(service); - if (t->service == NULL) { - error = GIT_ENOMEM; - goto cleanup; - } + GITERR_CHECK_ALLOC(t->service); - error = do_connect(t, t->host, t->port); - if (error < GIT_SUCCESS) { - error = git__rethrow(error, "Failed to connect to host"); + if ((ret = do_connect(t, t->host, t->port)) < 0) goto cleanup; - } /* Generate and send the HTTP request */ - error = gen_request(&request, url, t->host, "GET", service, 0, 1); - if (error < GIT_SUCCESS) { - error = git__throw(error, "Failed to generate request"); + if ((ret = gen_request(&request, url, t->host, "GET", service, 0, 1)) < 0) { + giterr_set(GITERR_NET, "Failed to generate request"); goto cleanup; } - error = gitno_send(t->socket, request.ptr, request.size, 0); - if (error < GIT_SUCCESS) - error = git__rethrow(error, "Failed to send the HTTP request"); + if ((ret = gitno_send(t->socket, request.ptr, request.size, 0)) < 0) + goto cleanup; - error = store_refs(t); + ret = store_refs(t); cleanup: git_buf_free(&request); git_buf_clear(&t->buf); - return error; + return ret; } static int http_ls(git_transport *transport, git_headlist_cb list_cb, void *opaque) @@ -309,12 +319,13 @@ static int http_ls(git_transport *transport, git_headlist_cb list_cb, void *opaq if (p->type != GIT_PKT_REF) continue; - if (list_cb(&p->head, opaque) < 0) - return git__throw(GIT_ERROR, - "The user callback returned an error code"); + if (list_cb(&p->head, opaque) < 0) { + giterr_set(GITERR_NET, "The user callback returned error"); + return -1; + } } - return GIT_SUCCESS; + return 0; } static int on_body_parse_response(http_parser *parser, const char *str, size_t len) @@ -326,10 +337,12 @@ static int on_body_parse_response(http_parser *parser, const char *str, size_t l const char *line_end, *ptr; if (len == 0) { /* EOF */ - if (buf->size != 0) - return t->error = git__throw(GIT_ERROR, "EOF and unprocessed data"); - else + if (git_buf_len(buf) != 0) { + giterr_set(GITERR_NET, "Unexpected EOF"); + return t->error = -1; + } else { return 0; + } } git_buf_put(buf, str, len); @@ -337,15 +350,15 @@ static int on_body_parse_response(http_parser *parser, const char *str, size_t l while (1) { git_pkt *pkt; - if (buf->size == 0) + if (git_buf_len(buf) == 0) return 0; - error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->size); + error = git_pkt_parse_line(&pkt, ptr, &line_end, git_buf_len(buf)); if (error == GIT_ESHORTBUFFER) { return 0; /* Ask for more */ } if (error < GIT_SUCCESS) - return t->error = git__rethrow(error, "Failed to parse pkt-line"); + return t->error = -1; git_buf_consume(buf, line_end); @@ -365,9 +378,8 @@ static int on_body_parse_response(http_parser *parser, const char *str, size_t l continue; } - error = git_vector_insert(common, pkt); - if (error < GIT_SUCCESS) - return t->error = git__rethrow(error, "Failed to add pkt to list"); + if (git_vector_insert(common, pkt) < 0) + return -1; } return error; @@ -376,7 +388,7 @@ static int on_body_parse_response(http_parser *parser, const char *str, size_t l static int parse_response(transport_http *t) { - int error = GIT_SUCCESS; + int ret = 0; http_parser_settings settings; char buffer[1024]; gitno_buffer buf; @@ -396,74 +408,28 @@ static int parse_response(transport_http *t) while(1) { size_t parsed; - error = gitno_recv(&buf); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Error receiving data from network"); + if ((ret = gitno_recv(&buf)) < 0) + return -1; parsed = http_parser_execute(&t->parser, &settings, buf.data, buf.offset); /* Both should happen at the same time */ - if (parsed != buf.offset || t->error < GIT_SUCCESS) - return git__rethrow(t->error, "Error parsing HTTP data"); + if (parsed != buf.offset || t->error < 0) + return t->error; gitno_consume_n(&buf, parsed); - if (error == 0 || t->transfer_finished || t->pack_ready) { - return GIT_SUCCESS; - } - } - - return error; -} - -static int setup_walk(git_revwalk **out, git_repository *repo) -{ - git_revwalk *walk; - git_strarray refs; - unsigned int i; - git_reference *ref; - int error; - - error = git_reference_listall(&refs, repo, GIT_REF_LISTALL); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to list references"); - - error = git_revwalk_new(&walk, repo); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to setup walk"); - - git_revwalk_sorting(walk, GIT_SORT_TIME); - - for (i = 0; i < refs.count; ++i) { - /* No tags */ - if (!git__prefixcmp(refs.strings[i], GIT_REFS_TAGS_DIR)) - continue; - - error = git_reference_lookup(&ref, repo, refs.strings[i]); - if (error < GIT_ERROR) { - error = git__rethrow(error, "Failed to lookup %s", refs.strings[i]); - goto cleanup; - } - - if (git_reference_type(ref) == GIT_REF_SYMBOLIC) - continue; - error = git_revwalk_push(walk, git_reference_oid(ref)); - if (error < GIT_ERROR) { - error = git__rethrow(error, "Failed to push %s", refs.strings[i]); - goto cleanup; + if (ret == 0 || t->transfer_finished || t->pack_ready) { + return 0; } } - *out = walk; -cleanup: - git_strarray_free(&refs); - - return error; + return ret; } static int http_negotiate_fetch(git_transport *transport, git_repository *repo, const git_vector *wants) { transport_http *t = (transport_http *) transport; - int error; + int ret; unsigned int i; char buff[128]; gitno_buffer buf; @@ -479,82 +445,55 @@ static int http_negotiate_fetch(git_transport *transport, git_repository *repo, if (!git__prefixcmp(url, prefix)) url += strlen(prefix); - error = git_vector_init(common, 16, NULL); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to init common vector"); + if (git_vector_init(common, 16, NULL) < 0) + return -1; - error = setup_walk(&walk, repo); - if (error < GIT_SUCCESS) { - error = git__rethrow(error, "Failed to setup walk"); - goto cleanup; - } + if (git_fetch_setup_walk(&walk, repo) < 0) + return -1; do { - error = do_connect(t, t->host, t->port); - if (error < GIT_SUCCESS) { - error = git__rethrow(error, "Failed to connect to host"); + if ((ret = do_connect(t, t->host, t->port)) < 0) goto cleanup; - } - error = git_pkt_buffer_wants(wants, &t->caps, &data); - if (error < GIT_SUCCESS) { - error = git__rethrow(error, "Failed to send wants"); + if ((ret = git_pkt_buffer_wants(wants, &t->caps, &data)) < 0) goto cleanup; - } /* We need to send these on each connection */ git_vector_foreach (common, i, pkt) { - error = git_pkt_buffer_have(&pkt->oid, &data); - if (error < GIT_SUCCESS) { - error = git__rethrow(error, "Failed to buffer common have"); + if ((ret = git_pkt_buffer_have(&pkt->oid, &data)) < 0) goto cleanup; - } } i = 0; - while ((i < 20) && ((error = git_revwalk_next(&oid, walk)) == GIT_SUCCESS)) { - error = git_pkt_buffer_have(&oid, &data); - if (error < GIT_SUCCESS) { - error = git__rethrow(error, "Failed to buffer have"); + while ((i < 20) && ((ret = git_revwalk_next(&oid, walk)) == 0)) { + if ((ret = git_pkt_buffer_have(&oid, &data)) < 0) goto cleanup; - } + i++; } git_pkt_buffer_done(&data); - error = gen_request(&request, url, t->host, "POST", "upload-pack", data.size, 0); - if (error < GIT_SUCCESS) { - error = git__rethrow(error, "Failed to generate request"); + if ((ret = gen_request(&request, url, t->host, "POST", "upload-pack", data.size, 0)) < 0) goto cleanup; - } - error = gitno_send(t->socket, request.ptr, request.size, 0); - if (error < GIT_SUCCESS) { - error = git__rethrow(error, "Failed to send request"); + if ((ret = gitno_send(t->socket, request.ptr, request.size, 0)) < 0) goto cleanup; - } - error = gitno_send(t->socket, data.ptr, data.size, 0); - if (error < GIT_SUCCESS) { - error = git__rethrow(error, "Failed to send data"); + if ((ret = gitno_send(t->socket, data.ptr, data.size, 0)) < 0) goto cleanup; - } git_buf_clear(&request); git_buf_clear(&data); - if (error < GIT_SUCCESS || i >= 256) + if (ret < GIT_SUCCESS || i >= 256) break; - error = parse_response(t); - if (error < GIT_SUCCESS) { - error = git__rethrow(error, "Error parsing the response"); + if ((ret = parse_response(t)) < 0) goto cleanup; - } if (t->pack_ready) { - error = GIT_SUCCESS; + ret = 0; goto cleanup; } @@ -564,11 +503,12 @@ cleanup: git_buf_free(&request); git_buf_free(&data); git_revwalk_free(walk); - return error; + return ret; } typedef struct { - git_filebuf *file; + git_indexer_stream *idx; + git_indexer_stats *stats; transport_http *transport; } download_pack_cbdata; @@ -584,10 +524,10 @@ static int on_body_download_pack(http_parser *parser, const char *str, size_t le { download_pack_cbdata *data = (download_pack_cbdata *) parser->data; transport_http *t = data->transport; - git_filebuf *file = data->file; + git_indexer_stream *idx = data->idx; + git_indexer_stats *stats = data->stats; - - return t->error = git_filebuf_write(file, str, len); + return t->error = git_indexer_stream_add(idx, str, len, stats); } /* @@ -596,96 +536,81 @@ static int on_body_download_pack(http_parser *parser, const char *str, size_t le * the simple downloader. Furthermore, we're using keep-alive * connections, so the simple downloader would just hang. */ -static int http_download_pack(char **out, git_transport *transport, git_repository *repo) +static int http_download_pack(git_transport *transport, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats) { transport_http *t = (transport_http *) transport; git_buf *oldbuf = &t->buf; - int error = GIT_SUCCESS; + int recvd; http_parser_settings settings; char buffer[1024]; gitno_buffer buf; + git_indexer_stream *idx = NULL; download_pack_cbdata data; - git_filebuf file = GIT_FILEBUF_INIT; - git_buf path = GIT_BUF_INIT; - char suff[] = "/objects/pack/pack-received\0"; + + gitno_buffer_setup(&buf, buffer, sizeof(buffer), t->socket); + + if (memcmp(oldbuf->ptr, "PACK", strlen("PACK"))) { + giterr_set(GITERR_NET, "The pack doesn't start with a pack signature"); + return -1; + } + + if (git_indexer_stream_new(&idx, git_repository_path(repo)) < 0) + return -1; + /* * This is part of the previous response, so we don't want to * re-init the parser, just set these two callbacks. */ - data.file = &file; + memset(stats, 0, sizeof(git_indexer_stats)); + data.stats = stats; + data.idx = idx; data.transport = t; t->parser.data = &data; t->transfer_finished = 0; memset(&settings, 0x0, sizeof(settings)); settings.on_message_complete = on_message_complete_download_pack; settings.on_body = on_body_download_pack; + *bytes = git_buf_len(oldbuf); - gitno_buffer_setup(&buf, buffer, sizeof(buffer), t->socket); + if (git_indexer_stream_add(idx, git_buf_cstr(oldbuf), git_buf_len(oldbuf), stats) < 0) + goto on_error; - if (memcmp(oldbuf->ptr, "PACK", strlen("PACK"))) { - return git__throw(GIT_ERROR, "The pack doesn't start with the signature"); - } - - error = git_buf_joinpath(&path, repo->path_repository, suff); - if (error < GIT_SUCCESS) - goto cleanup; - - error = git_filebuf_open(&file, path.ptr, GIT_FILEBUF_TEMPORARY); - if (error < GIT_SUCCESS) - goto cleanup; - - /* Part of the packfile has been received, don't loose it */ - error = git_filebuf_write(&file, oldbuf->ptr, oldbuf->size); - if (error < GIT_SUCCESS) - goto cleanup; - - while(1) { + do { size_t parsed; - error = gitno_recv(&buf); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Error receiving data from network"); + if ((recvd = gitno_recv(&buf)) < 0) + goto on_error; parsed = http_parser_execute(&t->parser, &settings, buf.data, buf.offset); - /* Both should happen at the same time */ - if (parsed != buf.offset || t->error < GIT_SUCCESS) - return git__rethrow(t->error, "Error parsing HTTP data"); + if (parsed != buf.offset || t->error < 0) + goto on_error; + *bytes += recvd; gitno_consume_n(&buf, parsed); + } while (recvd > 0 && !t->transfer_finished); - if (error == 0 || t->transfer_finished) { - break; - } - } - - *out = git__strdup(file.path_lock); - if (*out == NULL) { - error = GIT_ENOMEM; - goto cleanup; - } + if (git_indexer_stream_finalize(idx, stats) < 0) + goto on_error; - /* A bit dodgy, but we need to keep the pack at the temporary path */ - error = git_filebuf_commit_at(&file, file.path_lock, GIT_PACK_FILE_MODE); - -cleanup: - if (error < GIT_SUCCESS) - git_filebuf_cleanup(&file); - git_buf_free(&path); + git_indexer_stream_free(idx); + return 0; - return error; +on_error: + git_indexer_stream_free(idx); + return -1; } static int http_close(git_transport *transport) { transport_http *t = (transport_http *) transport; - int error; - error = gitno_close(t->socket); - if (error < 0) - return git__throw(GIT_EOSERR, "Failed to close the socket: %s", strerror(errno)); + if (gitno_close(t->socket) < 0) { + giterr_set(GITERR_OS, "Failed to close the socket: %s", strerror(errno)); + return -1; + } - return GIT_SUCCESS; + return 0; } @@ -729,8 +654,7 @@ int git_transport_http(git_transport **out) transport_http *t; t = git__malloc(sizeof(transport_http)); - if (t == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(t); memset(t, 0x0, sizeof(transport_http)); @@ -748,10 +672,11 @@ int git_transport_http(git_transport **out) * before any socket calls can be performed */ if (WSAStartup(MAKEWORD(2,2), &t->wsd) != 0) { http_free((git_transport *) t); - return git__throw(GIT_EOSERR, "Winsock init failed"); + giterr_set(GITERR_OS, "Winsock init failed"); + return -1; } #endif *out = (git_transport *) t; - return GIT_SUCCESS; + return 0; } diff --git a/src/transports/local.c b/src/transports/local.c index eb24db0fd..5dc350103 100644 --- a/src/transports/local.c +++ b/src/transports/local.c @@ -26,110 +26,94 @@ static int add_ref(transport_local *t, const char *name) { const char peeled[] = "^{}"; git_remote_head *head; - git_reference *ref, *resolved_ref; - git_object *obj = NULL; - int error = GIT_SUCCESS, peel_len, ret; + git_object *obj = NULL, *target = NULL; + git_buf buf = GIT_BUF_INIT; head = git__malloc(sizeof(git_remote_head)); - if (head == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(head); head->name = git__strdup(name); - if (head->name == NULL) { - error = GIT_ENOMEM; - goto out; - } - - error = git_reference_lookup(&ref, t->repo, name); - if (error < GIT_SUCCESS) - goto out; + GITERR_CHECK_ALLOC(head->name); - error = git_reference_resolve(&resolved_ref, ref); - if (error < GIT_SUCCESS) - goto out; - - git_oid_cpy(&head->oid, git_reference_oid(resolved_ref)); - - error = git_vector_insert(&t->refs, head); - if (error < GIT_SUCCESS) - goto out; + if (git_reference_name_to_oid(&head->oid, t->repo, name) < 0 || + git_vector_insert(&t->refs, head) < 0) + { + git__free(head->name); + git__free(head); + return -1; + } /* If it's not a tag, we don't need to try to peel it */ if (git__prefixcmp(name, GIT_REFS_TAGS_DIR)) - goto out; + return 0; - error = git_object_lookup(&obj, t->repo, &head->oid, GIT_OBJ_ANY); - if (error < GIT_SUCCESS) { - git__rethrow(error, "Failed to lookup object"); - } + if (git_object_lookup(&obj, t->repo, &head->oid, GIT_OBJ_ANY) < 0) + return -1; head = NULL; /* If it's not an annotated tag, just get out */ - if (git_object_type(obj) != GIT_OBJ_TAG) - goto out; + if (git_object_type(obj) != GIT_OBJ_TAG) { + git_object_free(obj); + return 0; + } /* And if it's a tag, peel it, and add it to the list */ head = git__malloc(sizeof(git_remote_head)); - peel_len = strlen(name) + strlen(peeled); - head->name = git__malloc(peel_len + 1); - ret = p_snprintf(head->name, peel_len + 1, "%s%s", name, peeled); + GITERR_CHECK_ALLOC(head); + if (git_buf_join(&buf, 0, name, peeled) < 0) + return -1; - assert(ret < peel_len + 1); - (void)ret; + head->name = git_buf_detach(&buf); - git_oid_cpy(&head->oid, git_tag_target_oid((git_tag *) obj)); + if (git_tag_peel(&target, (git_tag *) obj) < 0) + goto on_error; - error = git_vector_insert(&t->refs, head); - if (error < GIT_SUCCESS) - goto out; + git_oid_cpy(&head->oid, git_object_id(target)); + git_object_free(obj); + git_object_free(target); - out: - git_reference_free(ref); - git_reference_free(resolved_ref); + if (git_vector_insert(&t->refs, head) < 0) + return -1; - git_object_free(obj); - if (head && error < GIT_SUCCESS) { - git__free(head->name); - git__free(head); - } + return 0; - return error; +on_error: + git_object_free(obj); + git_object_free(target); + return -1; } static int store_refs(transport_local *t) { - int error; unsigned int i; git_strarray ref_names = {0}; assert(t); - error = git_vector_init(&t->refs, ref_names.count, NULL); - if (error < GIT_SUCCESS) - return error; - - error = git_reference_listall(&ref_names, t->repo, GIT_REF_LISTALL); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to list remote heads"); + if (git_reference_listall(&ref_names, t->repo, GIT_REF_LISTALL) < 0 || + git_vector_init(&t->refs, (unsigned int)ref_names.count, NULL) < 0) + goto on_error; /* Sort the references first */ git__tsort((void **)ref_names.strings, ref_names.count, &git__strcmp_cb); /* Add HEAD */ - error = add_ref(t, GIT_HEAD_FILE); - if (error < GIT_SUCCESS) - goto cleanup; + if (add_ref(t, GIT_HEAD_FILE) < 0) + goto on_error; for (i = 0; i < ref_names.count; ++i) { - error = add_ref(t, ref_names.strings[i]); - if (error < GIT_SUCCESS) - goto cleanup; + if (add_ref(t, ref_names.strings[i]) < 0) + goto on_error; } -cleanup: git_strarray_free(&ref_names); - return error; + return 0; + +on_error: + git_vector_free(&t->refs); + git_strarray_free(&ref_names); + return -1; } static int local_ls(git_transport *transport, git_headlist_cb list_cb, void *payload) @@ -143,11 +127,10 @@ static int local_ls(git_transport *transport, git_headlist_cb list_cb, void *pay git_vector_foreach(refs, i, h) { if (list_cb(h, payload) < 0) - return git__throw(GIT_ERROR, - "The user callback returned an error code"); + return -1; } - return GIT_SUCCESS; + return 0; } /* @@ -166,32 +149,31 @@ static int local_connect(git_transport *transport, int direction) /* The repo layer doesn't want the prefix */ if (!git__prefixcmp(transport->url, "file://")) { - error = git_path_fromurl(&buf, transport->url); - if (error < GIT_SUCCESS) { + if (git_path_fromurl(&buf, transport->url) < 0) { git_buf_free(&buf); - return git__rethrow(error, "Failed to parse remote path"); + return -1; } path = git_buf_cstr(&buf); - } else /* We assume transport->url is already a path */ + } else { /* We assume transport->url is already a path */ path = transport->url; + } error = git_repository_open(&repo, path); git_buf_free(&buf); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to open remote"); + if (error < 0) + return -1; t->repo = repo; - error = store_refs(t); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to retrieve references"); + if (store_refs(t) < 0) + return -1; t->parent.connected = 1; - return GIT_SUCCESS; + return 0; } static int local_close(git_transport *transport) @@ -201,7 +183,7 @@ static int local_close(git_transport *transport) git_repository_free(t->repo); t->repo = NULL; - return GIT_SUCCESS; + return 0; } static void local_free(git_transport *transport) @@ -232,8 +214,7 @@ int git_transport_local(git_transport **out) transport_local *t; t = git__malloc(sizeof(transport_local)); - if (t == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(t); memset(t, 0x0, sizeof(transport_local)); @@ -244,5 +225,5 @@ int git_transport_local(git_transport **out) *out = (git_transport *) t; - return GIT_SUCCESS; + return 0; } diff --git a/src/tree.c b/src/tree.c index 8a0971abd..09ed1a3e8 100644 --- a/src/tree.c +++ b/src/tree.c @@ -201,41 +201,38 @@ unsigned int git_tree_entrycount(git_tree *tree) return tree->entries.length; } -static int tree_parse_buffer(git_tree *tree, const char *buffer, const char *buffer_end) +static int tree_error(const char *str) { - int error = GIT_SUCCESS; + giterr_set(GITERR_TREE, "%s", str); + return -1; +} - if (git_vector_init(&tree->entries, DEFAULT_TREE_SIZE, entry_sort_cmp) < GIT_SUCCESS) - return GIT_ENOMEM; +static int tree_parse_buffer(git_tree *tree, const char *buffer, const char *buffer_end) +{ + if (git_vector_init(&tree->entries, DEFAULT_TREE_SIZE, entry_sort_cmp) < 0) + return -1; while (buffer < buffer_end) { git_tree_entry *entry; int tmp; entry = git__calloc(1, sizeof(git_tree_entry)); - if (entry == NULL) { - error = GIT_ENOMEM; - break; - } + GITERR_CHECK_ALLOC(entry); - if (git_vector_insert(&tree->entries, entry) < GIT_SUCCESS) - return GIT_ENOMEM; + if (git_vector_insert(&tree->entries, entry) < 0) + return -1; - if (git__strtol32(&tmp, buffer, &buffer, 8) < GIT_SUCCESS || + if (git__strtol32(&tmp, buffer, &buffer, 8) < 0 || !buffer || !valid_attributes(tmp)) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tree. Can't parse attributes"); + return tree_error("Failed to parse tree. Can't parse attributes"); entry->attr = tmp; - if (*buffer++ != ' ') { - error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse tree. Object it corrupted"); - break; - } + if (*buffer++ != ' ') + return tree_error("Failed to parse tree. Object is corrupted"); - if (memchr(buffer, 0, buffer_end - buffer) == NULL) { - error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse tree. Object it corrupted"); - break; - } + if (memchr(buffer, 0, buffer_end - buffer) == NULL) + return tree_error("Failed to parse tree. Object is corrupted"); entry->filename = git__strdup(buffer); entry->filename_len = strlen(buffer); @@ -249,7 +246,7 @@ static int tree_parse_buffer(git_tree *tree, const char *buffer, const char *buf buffer += GIT_OID_RAWSZ; } - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse buffer"); + return 0; } int git_tree__parse(git_tree *tree, git_odb_object *obj) @@ -280,10 +277,9 @@ static int append_entry(git_treebuilder *bld, const char *filename, const git_oi { git_tree_entry *entry; - if ((entry = git__malloc(sizeof(git_tree_entry))) == NULL) - return GIT_ENOMEM; + entry = git__calloc(1, sizeof(git_tree_entry)); + GITERR_CHECK_ALLOC(entry); - memset(entry, 0x0, sizeof(git_tree_entry)); entry->filename = git__strdup(filename); entry->filename_len = strlen(entry->filename); @@ -291,9 +287,9 @@ static int append_entry(git_treebuilder *bld, const char *filename, const git_oi entry->attr = attributes; if (git_vector_insert(&bld->entries, entry) < 0) - return GIT_ENOMEM; + return -1; - return GIT_SUCCESS; + return 0; } static int write_tree( @@ -318,7 +314,7 @@ static int write_tree( error = git_treebuilder_create(&bld, NULL); if (bld == NULL) { - return GIT_ENOMEM; + return -1; } /* @@ -354,16 +350,13 @@ static int write_tree( char *subdir, *last_comp; subdir = git__strndup(entry->path, next_slash - entry->path); - if (subdir == NULL) { - error = GIT_ENOMEM; - goto cleanup; - } + GITERR_CHECK_ALLOC(subdir); /* Write out the subtree */ written = write_tree(&sub_oid, repo, index, subdir, i); if (written < 0) { - error = git__rethrow(written, "Failed to write subtree %s", subdir); - goto cleanup; + tree_error("Failed to write subtree"); + goto on_error; } else { i = written - 1; /* -1 because of the loop increment */ } @@ -382,51 +375,49 @@ static int write_tree( } error = append_entry(bld, last_comp, &sub_oid, S_IFDIR); git__free(subdir); - if (error < GIT_SUCCESS) { - error = git__rethrow(error, "Failed to insert dir"); - goto cleanup; + if (error < 0) { + tree_error("Failed to insert dir"); + goto on_error; } } else { error = append_entry(bld, filename, &entry->oid, entry->mode); - if (error < GIT_SUCCESS) { - error = git__rethrow(error, "Failed to insert file"); + if (error < 0) { + tree_error("Failed to insert file"); + goto on_error; } } } - error = git_treebuilder_write(oid, repo, bld); - if (error < GIT_SUCCESS) - error = git__rethrow(error, "Failed to write tree to db"); + if (git_treebuilder_write(oid, repo, bld) < 0) + goto on_error; - cleanup: git_treebuilder_free(bld); + return i; - if (error < GIT_SUCCESS) - return error; - else - return i; +on_error: + git_treebuilder_free(bld); + return -1; } int git_tree_create_fromindex(git_oid *oid, git_index *index) { + int ret; git_repository *repo; - int error; repo = (git_repository *)GIT_REFCOUNT_OWNER(index); if (repo == NULL) - return git__throw(GIT_EBAREINDEX, - "Failed to create tree. " - "The index file is not backed up by an existing repository"); + return tree_error("Failed to create tree. " + "The index file is not backed up by an existing repository"); if (index->tree != NULL && index->tree->entries >= 0) { git_oid_cpy(oid, &index->tree->oid); - return GIT_SUCCESS; + return 0; } /* The tree cache didn't help us */ - error = write_tree(oid, repo, index, "", 0); - return (error < GIT_SUCCESS) ? git__rethrow(error, "Failed to create tree") : GIT_SUCCESS; + ret = write_tree(oid, repo, index, "", 0); + return ret < 0 ? ret : 0; } static void sort_entries(git_treebuilder *bld) @@ -442,30 +433,29 @@ int git_treebuilder_create(git_treebuilder **builder_p, const git_tree *source) assert(builder_p); bld = git__calloc(1, sizeof(git_treebuilder)); - if (bld == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(bld); if (source != NULL) source_entries = source->entries.length; - if (git_vector_init(&bld->entries, source_entries, entry_sort_cmp) < GIT_SUCCESS) { - git__free(bld); - return GIT_ENOMEM; - } + if (git_vector_init(&bld->entries, source_entries, entry_sort_cmp) < 0) + goto on_error; if (source != NULL) { for (i = 0; i < source->entries.length; ++i) { git_tree_entry *entry_src = source->entries.contents[i]; - if (append_entry(bld, entry_src->filename, &entry_src->oid, entry_src->attr) < 0) { - git_treebuilder_free(bld); - return GIT_ENOMEM; - } + if (append_entry(bld, entry_src->filename, &entry_src->oid, entry_src->attr) < 0) + goto on_error; } } *builder_p = bld; - return GIT_SUCCESS; + return 0; + +on_error: + git_treebuilder_free(bld); + return -1; } int git_treebuilder_insert(git_tree_entry **entry_out, git_treebuilder *bld, const char *filename, const git_oid *id, unsigned int attributes) @@ -476,10 +466,10 @@ int git_treebuilder_insert(git_tree_entry **entry_out, git_treebuilder *bld, con assert(bld && id && filename); if (!valid_attributes(attributes)) - return git__throw(GIT_ERROR, "Failed to insert entry. Invalid attributes"); + return tree_error("Failed to insert entry. Invalid attributes"); if (!valid_entry_name(filename)) - return git__throw(GIT_ERROR, "Failed to insert entry. Invalid name for a tree entry"); + return tree_error("Failed to insert entry. Invalid name for a tree entry"); pos = tree_key_search(&bld->entries, filename); @@ -488,10 +478,9 @@ int git_treebuilder_insert(git_tree_entry **entry_out, git_treebuilder *bld, con if (entry->removed) entry->removed = 0; } else { - if ((entry = git__malloc(sizeof(git_tree_entry))) == NULL) - return GIT_ENOMEM; + entry = git__calloc(1, sizeof(git_tree_entry)); + GITERR_CHECK_ALLOC(entry); - memset(entry, 0x0, sizeof(git_tree_entry)); entry->filename = git__strdup(filename); entry->filename_len = strlen(entry->filename); } @@ -501,13 +490,13 @@ int git_treebuilder_insert(git_tree_entry **entry_out, git_treebuilder *bld, con if (pos == GIT_ENOTFOUND) { if (git_vector_insert(&bld->entries, entry) < 0) - return GIT_ENOMEM; + return -1; } if (entry_out != NULL) *entry_out = entry; - return GIT_SUCCESS; + return 0; } static git_tree_entry *treebuilder_get(git_treebuilder *bld, const char *filename) @@ -538,16 +527,15 @@ int git_treebuilder_remove(git_treebuilder *bld, const char *filename) git_tree_entry *remove_ptr = treebuilder_get(bld, filename); if (remove_ptr == NULL || remove_ptr->removed) - return git__throw(GIT_ENOTFOUND, "Failed to remove entry. File isn't in the tree"); + return tree_error("Failed to remove entry. File isn't in the tree"); remove_ptr->removed = 1; - return GIT_SUCCESS; + return 0; } int git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *bld) { unsigned int i; - int error; git_buf tree = GIT_BUF_INIT; git_odb *odb; @@ -569,21 +557,22 @@ int git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *b git_buf_put(&tree, (char *)entry->oid.id, GIT_OID_RAWSZ); } - if ((error = git_buf_lasterror(&tree)) < GIT_SUCCESS) { - git_buf_free(&tree); - return git__rethrow(error, "Not enough memory to build the tree data"); - } + if (git_buf_oom(&tree)) + goto on_error; - error = git_repository_odb__weakptr(&odb, repo); - if (error < GIT_SUCCESS) { - git_buf_free(&tree); - return error; - } + if (git_repository_odb__weakptr(&odb, repo) < 0) + goto on_error; + + + if (git_odb_write(oid, odb, tree.ptr, tree.size, GIT_OBJ_TREE) < 0) + goto on_error; - error = git_odb_write(oid, odb, tree.ptr, tree.size, GIT_OBJ_TREE); git_buf_free(&tree); + return 0; - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write tree"); +on_error: + git_buf_free(&tree); + return -1; } void git_treebuilder_filter(git_treebuilder *bld, int (*filter)(const git_tree_entry *, void *), void *payload) @@ -624,16 +613,18 @@ static int tree_frompath( git_tree **parent_out, git_tree *root, git_buf *treeentry_path, - int offset) + size_t offset) { char *slash_pos = NULL; const git_tree_entry* entry; int error = GIT_SUCCESS; git_tree *subtree; - if (!*(treeentry_path->ptr + offset)) - return git__rethrow(GIT_EINVALIDPATH, + if (!*(treeentry_path->ptr + offset)) { + giterr_set(GITERR_INVALID, "Invalid relative path to a tree entry '%s'.", treeentry_path->ptr); + return -1; + } slash_pos = (char *)strchr(treeentry_path->ptr + offset, '/'); @@ -644,9 +635,11 @@ static int tree_frompath( git_object_id((const git_object *)root) ); - if (slash_pos == treeentry_path->ptr + offset) - return git__rethrow(GIT_EINVALIDPATH, + if (slash_pos == treeentry_path->ptr + offset) { + giterr_set(GITERR_INVALID, "Invalid relative path to a tree entry '%s'.", treeentry_path->ptr); + return -1; + } *slash_pos = '\0'; @@ -655,14 +648,15 @@ static int tree_frompath( if (slash_pos != NULL) *slash_pos = '/'; - if (entry == NULL) - return git__rethrow(GIT_ENOTFOUND, + if (entry == NULL) { + giterr_set(GITERR_TREE, "No tree entry can be found from " "the given tree and relative path '%s'.", treeentry_path->ptr); + return GIT_ENOTFOUND; + } - error = git_tree_lookup(&subtree, root->object.repo, &entry->oid); - if (error < GIT_SUCCESS) + if (git_tree_lookup(&subtree, root->object.repo, &entry->oid) < 0) return error; error = tree_frompath( @@ -686,7 +680,7 @@ int git_tree_get_subtree( assert(subtree && root && subtree_path); - if ((error = git_buf_sets(&buffer, subtree_path)) == GIT_SUCCESS) + if ((error = git_buf_sets(&buffer, subtree_path)) == 0) error = tree_frompath(subtree, root, &buffer, 0); git_buf_free(&buffer); @@ -711,7 +705,7 @@ static int tree_walk_post( if (entry_is_tree(entry)) { git_tree *subtree; - size_t path_len = path->size; + size_t path_len = git_buf_len(path); if ((error = git_tree_lookup( &subtree, tree->object.repo, &entry->oid)) < 0) @@ -720,19 +714,19 @@ static int tree_walk_post( /* append the next entry to the path */ git_buf_puts(path, entry->filename); git_buf_putc(path, '/'); - if ((error = git_buf_lasterror(path)) < GIT_SUCCESS) - break; - error = tree_walk_post(subtree, callback, path, payload); - if (error < GIT_SUCCESS) - break; + if (git_buf_oom(path)) + return -1; + + if (tree_walk_post(subtree, callback, path, payload) < 0) + return -1; git_buf_truncate(path, path_len); git_tree_free(subtree); } } - return error; + return 0; } int git_tree_walk(git_tree *tree, git_treewalk_cb callback, int mode, void *payload) @@ -746,14 +740,12 @@ int git_tree_walk(git_tree *tree, git_treewalk_cb callback, int mode, void *payl break; case GIT_TREEWALK_PRE: - error = git__throw(GIT_ENOTIMPLEMENTED, - "Preorder tree walking is still not implemented"); - break; + tree_error("Preorder tree walking is still not implemented"); + return GIT_ENOTIMPLEMENTED; default: - error = git__throw(GIT_EINVALIDARGS, - "Invalid walking mode for tree walk"); - break; + giterr_set(GITERR_INVALID, "Invalid walking mode for tree walk"); + return -1; } git_buf_free(&root_path); @@ -792,7 +784,7 @@ static int signal_additions(git_tree *tree, int start, int end, git_tree_diff_cb { git_tree_diff_data diff; git_tree_entry *entry; - int i, error; + int i; if (end < 0) end = git_tree_entrycount(tree); @@ -802,12 +794,11 @@ static int signal_additions(git_tree *tree, int start, int end, git_tree_diff_cb entry = git_vector_get(&tree->entries, i); mark_add(&diff, entry); - error = cb(&diff, data); - if (error < GIT_SUCCESS) - return error; + if (cb(&diff, data) < 0) + return -1; } - return GIT_SUCCESS; + return 0; } static int signal_addition(git_tree_entry *entry, git_tree_diff_cb cb, void *data) @@ -825,7 +816,7 @@ static int signal_deletions(git_tree *tree, int start, int end, git_tree_diff_cb { git_tree_diff_data diff; git_tree_entry *entry; - int i, error; + int i; if (end < 0) end = git_tree_entrycount(tree); @@ -835,12 +826,11 @@ static int signal_deletions(git_tree *tree, int start, int end, git_tree_diff_cb entry = git_vector_get(&tree->entries, i); mark_del(&diff, entry); - error = cb(&diff, data); - if (error < GIT_SUCCESS) - return error; + if (cb(&diff, data) < 0) + return -1; } - return GIT_SUCCESS; + return 0; } static int signal_deletion(git_tree_entry *entry, git_tree_diff_cb cb, void *data) @@ -872,14 +862,14 @@ int git_tree_diff(git_tree *a, git_tree *b, git_tree_diff_cb cb, void *data) unsigned int i_a = 0, i_b = 0; /* Counters for trees a and b */ git_tree_entry *entry_a = NULL, *entry_b = NULL; git_tree_diff_data diff; - int error = GIT_SUCCESS, cmp; + int cmp; while (1) { entry_a = a == NULL ? NULL : git_vector_get(&a->entries, i_a); entry_b = b == NULL ? NULL : git_vector_get(&b->entries, i_b); if (!entry_a && !entry_b) - goto exit; + return 0; memset(&diff, 0x0, sizeof(git_tree_diff_data)); @@ -910,29 +900,28 @@ int git_tree_diff(git_tree *a, git_tree *b, git_tree_diff_cb cb, void *data) /* If they're not both dirs or both files, it's add + del */ if (S_ISDIR(entry_a->attr) != S_ISDIR(entry_b->attr)) { - if ((error = signal_addition(entry_a, cb, data)) < 0) - goto exit; - if ((error = signal_deletion(entry_b, cb, data)) < 0) - goto exit; + if (signal_addition(entry_a, cb, data) < 0) + return -1; + if (signal_deletion(entry_b, cb, data) < 0) + return -1; } /* Otherwise consider it a modification */ - if ((error = signal_modification(entry_a, entry_b, cb, data)) < 0) - goto exit; + if (signal_modification(entry_a, entry_b, cb, data) < 0) + return -1; } else if (cmp < 0) { i_a++; - if ((error = signal_deletion(entry_a, cb, data)) < 0) - goto exit; + if (signal_deletion(entry_a, cb, data) < 0) + return -1; } else if (cmp > 0) { i_b++; - if ((error = signal_addition(entry_b, cb, data)) < 0) - goto exit; + if (signal_addition(entry_b, cb, data) < 0) + return -1; } } -exit: - return error; + return 0; } struct diff_index_cbdata { @@ -977,53 +966,49 @@ static int diff_index_cb(const char *root, git_tree_entry *tentry, void *data) git_index_entry *ientry = git_index_get(cbdata->index, cbdata->i); git_tree_entry fake_entry; git_buf fn_buf = GIT_BUF_INIT; - int cmp, error = GIT_SUCCESS; + int cmp; if (entry_is_tree(tentry)) - return GIT_SUCCESS; + return 0; + + if (!ientry) + return signal_deletion(tentry, cbdata->cb, cbdata->data); git_buf_puts(&fn_buf, root); git_buf_puts(&fn_buf, tentry->filename); - if (!ientry) { - error = signal_deletion(tentry, cbdata->cb, cbdata->data); - git_buf_free(&fn_buf); - goto exit; - } - /* Like with 'git diff-index', the index is the right side*/ cmp = strcmp(git_buf_cstr(&fn_buf), ientry->path); git_buf_free(&fn_buf); if (cmp == 0) { cbdata->i++; if (!cmp_tentry_ientry(tentry, ientry)) - goto exit; + return 0; /* modification */ make_tentry(&fake_entry, ientry); - if ((error = signal_modification(tentry, &fake_entry, cbdata->cb, cbdata->data)) < 0) - goto exit; + if (signal_modification(tentry, &fake_entry, cbdata->cb, cbdata->data) < 0) + return -1; } else if (cmp < 0) { /* deletion */ memcpy(&fake_entry, tentry, sizeof(git_tree_entry)); - if ((error = signal_deletion(tentry, cbdata->cb, cbdata->data)) < 0) - goto exit; + if (signal_deletion(tentry, cbdata->cb, cbdata->data) < 0) + return -1; } else { /* addition */ cbdata->i++; make_tentry(&fake_entry, ientry); - if ((error = signal_addition(&fake_entry, cbdata->cb, cbdata->data)) < 0) - goto exit; + if (signal_addition(&fake_entry, cbdata->cb, cbdata->data) < 0) + return -1; /* * The index has an addition. This means that we need to use * the next entry in the index without advancing the tree * walker, so call ourselves with the same tree state. */ - if ((error = diff_index_cb(root, tentry, data)) < 0) - goto exit; + if (diff_index_cb(root, tentry, data) < 0) + return -1;; } - exit: - return error; + return 0; } int git_tree_diff_index_recursive(git_tree *tree, git_index *index, git_tree_diff_cb cb, void *data) diff --git a/src/tree.h b/src/tree.h index 0bff41312..fd00afde5 100644 --- a/src/tree.h +++ b/src/tree.h @@ -32,7 +32,7 @@ struct git_treebuilder { GIT_INLINE(unsigned int) entry_is_tree(const struct git_tree_entry *e) { - return e->attr & 040000; + return (S_ISDIR(e->attr) && !S_ISGITLINK(e->attr)); } void git_tree__free(git_tree *tree); diff --git a/src/tsort.c b/src/tsort.c index 6fbec5b2a..f54c21e50 100644 --- a/src/tsort.c +++ b/src/tsort.c @@ -30,8 +30,10 @@ static int binsearch(void **dst, const void *x, size_t size, cmp_ptr_t cmp) int l, c, r; void *lx, *cx; + assert(size > 0); + l = 0; - r = size - 1; + r = (int)size - 1; c = r >> 1; lx = dst[l]; @@ -84,7 +86,7 @@ static void bisort(void **dst, size_t start, size_t size, cmp_ptr_t cmp) /* Else we need to find the right place, shift everything over, and squeeze in */ x = dst[i]; location = binsearch(dst, x, i, cmp); - for (j = i - 1; j >= location; j--) { + for (j = (int)i - 1; j >= location; j--) { dst[j + 1] = dst[j]; } dst[location] = x; @@ -104,7 +106,7 @@ struct tsort_store { void **storage; }; -static void reverse_elements(void **dst, int start, int end) +static void reverse_elements(void **dst, ssize_t start, ssize_t end) { while (start < end) { void *tmp = dst[start]; @@ -116,7 +118,7 @@ static void reverse_elements(void **dst, int start, int end) } } -static int count_run(void **dst, ssize_t start, ssize_t size, struct tsort_store *store) +static ssize_t count_run(void **dst, ssize_t start, ssize_t size, struct tsort_store *store) { ssize_t curr = start + 2; @@ -148,7 +150,7 @@ static int count_run(void **dst, ssize_t start, ssize_t size, struct tsort_store } } -static int compute_minrun(size_t n) +static size_t compute_minrun(size_t n) { int r = 0; while (n >= 64) { @@ -158,19 +160,19 @@ static int compute_minrun(size_t n) return n + r; } -static int check_invariant(struct tsort_run *stack, int stack_curr) +static int check_invariant(struct tsort_run *stack, ssize_t stack_curr) { if (stack_curr < 2) return 1; else if (stack_curr == 2) { - const int A = stack[stack_curr - 2].length; - const int B = stack[stack_curr - 1].length; + const ssize_t A = stack[stack_curr - 2].length; + const ssize_t B = stack[stack_curr - 1].length; return (A > B); } else { - const int A = stack[stack_curr - 3].length; - const int B = stack[stack_curr - 2].length; - const int C = stack[stack_curr - 1].length; + const ssize_t A = stack[stack_curr - 3].length; + const ssize_t B = stack[stack_curr - 2].length; + const ssize_t C = stack[stack_curr - 1].length; return !((A <= B + C) || (B <= C)); } } @@ -195,7 +197,7 @@ static int resize(struct tsort_store *store, size_t new_size) return 0; } -static void merge(void **dst, const struct tsort_run *stack, int stack_curr, struct tsort_store *store) +static void merge(void **dst, const struct tsort_run *stack, ssize_t stack_curr, struct tsort_store *store) { const ssize_t A = stack[stack_curr - 2].length; const ssize_t B = stack[stack_curr - 1].length; @@ -343,7 +345,7 @@ void git__tsort(void **dst, size_t size, cmp_ptr_t cmp) } /* compute the minimum run length */ - minrun = compute_minrun(size); + minrun = (ssize_t)compute_minrun(size); /* temporary storage for merges */ store->alloc = 0; diff --git a/src/unix/map.c b/src/unix/map.c index 67a73e43c..772f4e247 100644 --- a/src/unix/map.c +++ b/src/unix/map.c @@ -17,12 +17,7 @@ int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offs int mprot = 0; int mflag = 0; - assert((out != NULL) && (len > 0)); - - if ((out == NULL) || (len == 0)) { - errno = EINVAL; - return git__throw(GIT_ERROR, "Failed to mmap. No map or zero length"); - } + GIT_MMAP_VALIDATE(out, len, prot, flags); out->data = NULL; out->len = 0; @@ -31,39 +26,28 @@ int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offs mprot = PROT_WRITE; else if (prot & GIT_PROT_READ) mprot = PROT_READ; - else { - errno = EINVAL; - return git__throw(GIT_ERROR, "Failed to mmap. Invalid protection parameters"); - } if ((flags & GIT_MAP_TYPE) == GIT_MAP_SHARED) mflag = MAP_SHARED; else if ((flags & GIT_MAP_TYPE) == GIT_MAP_PRIVATE) mflag = MAP_PRIVATE; - if (flags & GIT_MAP_FIXED) { - errno = EINVAL; - return git__throw(GIT_ERROR, "Failed to mmap. FIXED not set"); + out->data = mmap(NULL, len, mprot, mflag, fd, offset); + if (!out->data || out->data == MAP_FAILED) { + giterr_set(GITERR_OS, "Failed to mmap. Could not write data"); + return -1; } - out->data = mmap(NULL, len, mprot, mflag, fd, offset); - if (!out->data || out->data == MAP_FAILED) - return git__throw(GIT_EOSERR, "Failed to mmap. Could not write data"); out->len = len; - return GIT_SUCCESS; + return 0; } int p_munmap(git_map *map) { assert(map != NULL); - - if (!map) - return git__throw(GIT_ERROR, "Failed to munmap. Map does not exist"); - munmap(map->data, map->len); - - return GIT_SUCCESS; + return 0; } #endif diff --git a/src/util.c b/src/util.c index 1fb01170b..2cf7b158b 100644 --- a/src/util.c +++ b/src/util.c @@ -31,6 +31,35 @@ void git_strarray_free(git_strarray *array) git__free(array->strings); } +int git_strarray_copy(git_strarray *tgt, const git_strarray *src) +{ + size_t i; + + assert(tgt && src); + + memset(tgt, 0, sizeof(*tgt)); + + if (!src->count) + return 0; + + tgt->strings = git__calloc(src->count, sizeof(char *)); + GITERR_CHECK_ALLOC(tgt->strings); + + for (i = 0; i < src->count; ++i) { + tgt->strings[tgt->count] = git__strdup(src->strings[i]); + + if (!tgt->strings[tgt->count]) { + git_strarray_free(tgt); + memset(tgt, 0, sizeof(*tgt)); + return -1; + } + + tgt->count++; + } + + return 0; +} + int git__fnmatch(const char *pattern, const char *name, int flags) { int ret; @@ -38,11 +67,12 @@ int git__fnmatch(const char *pattern, const char *name, int flags) ret = p_fnmatch(pattern, name, flags); switch (ret) { case 0: - return GIT_SUCCESS; + return 0; case FNM_NOMATCH: return GIT_ENOMATCH; default: - return git__throw(GIT_EOSERR, "Error trying to match path"); + giterr_set(GITERR_OS, "Error trying to match path"); + return -1; } } @@ -111,34 +141,40 @@ int git__strtol64(int64_t *result, const char *nptr, const char **endptr, int ba } Return: - if (ndig == 0) - return git__throw(GIT_ENOTNUM, "Failed to convert string to long. Not a number"); + if (ndig == 0) { + giterr_set(GITERR_INVALID, "Failed to convert string to long. Not a number"); + return -1; + } if (endptr) *endptr = p; - if (ovfl) - return git__throw(GIT_EOVERFLOW, "Failed to convert string to long. Overflow error"); + if (ovfl) { + giterr_set(GITERR_INVALID, "Failed to convert string to long. Overflow error"); + return -1; + } *result = neg ? -n : n; - return GIT_SUCCESS; + return 0; } int git__strtol32(int32_t *result, const char *nptr, const char **endptr, int base) { - int error = GIT_SUCCESS; + int error; int32_t tmp_int; int64_t tmp_long; - if ((error = git__strtol64(&tmp_long, nptr, endptr, base)) < GIT_SUCCESS) + if ((error = git__strtol64(&tmp_long, nptr, endptr, base)) < 0) return error; tmp_int = tmp_long & 0xFFFFFFFF; - if (tmp_int != tmp_long) - return git__throw(GIT_EOVERFLOW, "Failed to convert. '%s' is too large", nptr); + if (tmp_int != tmp_long) { + giterr_set(GITERR_INVALID, "Failed to convert. '%s' is too large", nptr); + return -1; + } *result = tmp_int; - + return error; } @@ -355,23 +391,27 @@ int git__bsearch( int (*compare)(const void *, const void *), size_t *position) { - int lim, cmp; + unsigned int lim; + int cmp = -1; void **part, **base = array; - for (lim = array_len; lim != 0; lim >>= 1) { + for (lim = (unsigned int)array_len; lim != 0; lim >>= 1) { part = base + (lim >> 1); cmp = (*compare)(key, *part); if (cmp == 0) { - *position = (part - array); - return GIT_SUCCESS; - } else if (cmp > 0) { /* key > p; take right partition */ + base = part; + break; + } + if (cmp > 0) { /* key > p; take right partition */ base = part + 1; lim--; } /* else take left partition */ } - *position = (base - array); - return GIT_ENOTFOUND; + if (position) + *position = (base - array); + + return (cmp == 0) ? 0 : -1; } /** diff --git a/src/util.h b/src/util.h index f77c91dfd..1fee9a70c 100644 --- a/src/util.h +++ b/src/util.h @@ -22,24 +22,21 @@ GIT_INLINE(void *) git__malloc(size_t len) { void *ptr = malloc(len); - if (!ptr) - git__throw(GIT_ENOMEM, "Out of memory. Failed to allocate %d bytes.", (int)len); + if (!ptr) giterr_set_oom(); return ptr; } GIT_INLINE(void *) git__calloc(size_t nelem, size_t elsize) { void *ptr = calloc(nelem, elsize); - if (!ptr) - git__throw(GIT_ENOMEM, "Out of memory. Failed to allocate %d bytes.", (int)elsize); + if (!ptr) giterr_set_oom(); return ptr; } GIT_INLINE(char *) git__strdup(const char *str) { char *ptr = strdup(str); - if (!ptr) - git__throw(GIT_ENOMEM, "Out of memory. Failed to duplicate string"); + if (!ptr) giterr_set_oom(); return ptr; } @@ -54,7 +51,7 @@ GIT_INLINE(char *) git__strndup(const char *str, size_t n) ptr = (char*)malloc(length + 1); if (!ptr) { - git__throw(GIT_ENOMEM, "Out of memory. Failed to duplicate string"); + giterr_set_oom(); return NULL; } @@ -67,8 +64,7 @@ GIT_INLINE(char *) git__strndup(const char *str, size_t n) GIT_INLINE(void *) git__realloc(void *ptr, size_t size) { void *new_ptr = realloc(ptr, size); - if (!new_ptr) - git__throw(GIT_ENOMEM, "Out of memory. Failed to allocate %d bytes.", (int)size); + if (!new_ptr) giterr_set_oom(); return new_ptr; } @@ -113,6 +109,11 @@ extern int git__fnmatch(const char *pattern, const char *name, int flags); extern void git__tsort(void **dst, size_t size, int (*cmp)(const void *, const void *)); +/** + * @param position If non-NULL, this will be set to the position where the + * element is or would be inserted if not found. + * @return pos (>=0) if found or -1 if not found + */ extern int git__bsearch( void **array, size_t array_len, @@ -178,4 +179,21 @@ GIT_INLINE(int) git__ishex(const char *str) return 1; } +GIT_INLINE(size_t) git__size_t_bitmask(size_t v) +{ + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + + return v; +} + +GIT_INLINE(size_t) git__size_t_powerof2(size_t v) +{ + return git__size_t_bitmask(v) + 1; +} + #endif /* INCLUDE_util_h__ */ diff --git a/src/vector.c b/src/vector.c index 7513ea3f0..304f324f0 100644 --- a/src/vector.c +++ b/src/vector.c @@ -10,7 +10,7 @@ #include "vector.h" static const double resize_factor = 1.75; -static const size_t minimum_size = 8; +static const unsigned int minimum_size = 8; static int resize_vector(git_vector *v) { @@ -19,10 +19,9 @@ static int resize_vector(git_vector *v) v->_alloc_size = minimum_size; v->contents = git__realloc(v->contents, v->_alloc_size * sizeof(void *)); - if (v->contents == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(v->contents); - return GIT_SUCCESS; + return 0; } void git_vector_free(git_vector *v) @@ -52,30 +51,29 @@ int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp v->sorted = 1; v->contents = git__malloc(v->_alloc_size * sizeof(void *)); - if (v->contents == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(v->contents); - return GIT_SUCCESS; + return 0; } int git_vector_insert(git_vector *v, void *element) { assert(v); - if (v->length >= v->_alloc_size) { - if (resize_vector(v) < 0) - return GIT_ENOMEM; - } + if (v->length >= v->_alloc_size && + resize_vector(v) < 0) + return -1; v->contents[v->length++] = element; v->sorted = 0; - return GIT_SUCCESS; + return 0; } -int git_vector_insert_sorted(git_vector *v, void *element, int (*on_dup)(void **old, void *new)) +int git_vector_insert_sorted( + git_vector *v, void *element, int (*on_dup)(void **old, void *new)) { - int error = GIT_SUCCESS; + int result; size_t pos; assert(v && v->_cmp); @@ -83,22 +81,18 @@ int git_vector_insert_sorted(git_vector *v, void *element, int (*on_dup)(void ** if (!v->sorted) git_vector_sort(v); - if (v->length >= v->_alloc_size) { - if (resize_vector(v) < 0) - return GIT_ENOMEM; - } - - error = git__bsearch(v->contents, v->length, element, v->_cmp, &pos); + if (v->length >= v->_alloc_size && + resize_vector(v) < 0) + return -1; - /* If we found the element and have a duplicate handler callback, - * invoke it. If it returns an error, then cancel insert, otherwise + /* If we find the element and have a duplicate handler callback, + * invoke it. If it returns non-zero, then cancel insert, otherwise * proceed with normal insert. */ - if (error == GIT_SUCCESS && on_dup != NULL) { - error = on_dup(&v->contents[pos], element); - if (error != GIT_SUCCESS) - return error; - } + if (git__bsearch(v->contents, v->length, element, v->_cmp, &pos) >= 0 && + on_dup != NULL && + (result = on_dup(&v->contents[pos], element)) < 0) + return result; /* shift elements to the right */ if (pos < v->length) { @@ -108,8 +102,7 @@ int git_vector_insert_sorted(git_vector *v, void *element, int (*on_dup)(void ** v->contents[pos] = element; v->length++; - - return GIT_SUCCESS; + return 0; } void git_vector_sort(git_vector *v) @@ -130,16 +123,14 @@ int git_vector_bsearch2(git_vector *v, git_vector_cmp key_lookup, const void *ke assert(v && key && key_lookup); /* need comparison function to sort the vector */ - if (v->_cmp == NULL) - return git__throw(GIT_ENOTFOUND, "Can't sort vector. No comparison function set"); + assert(v->_cmp != NULL); git_vector_sort(v); - if (git__bsearch(v->contents, v->length, key, key_lookup, - &pos) == GIT_SUCCESS) + if (git__bsearch(v->contents, v->length, key, key_lookup, &pos) >= 0) return (int)pos; - return git__throw(GIT_ENOTFOUND, "Can't find element"); + return GIT_ENOTFOUND; } int git_vector_search2(git_vector *v, git_vector_cmp key_lookup, const void *key) @@ -153,7 +144,7 @@ int git_vector_search2(git_vector *v, git_vector_cmp key_lookup, const void *key return i; } - return git__throw(GIT_ENOTFOUND, "Can't find element"); + return GIT_ENOTFOUND; } static int strict_comparison(const void *a, const void *b) @@ -178,13 +169,13 @@ int git_vector_remove(git_vector *v, unsigned int idx) assert(v); if (idx >= v->length || v->length == 0) - return git__throw(GIT_ENOTFOUND, "Can't remove element. Index out of bounds"); + return GIT_ENOTFOUND; for (i = idx; i < v->length - 1; ++i) v->contents[i] = v->contents[i + 1]; v->length--; - return GIT_SUCCESS; + return 0; } void git_vector_pop(git_vector *v) diff --git a/src/vector.h b/src/vector.h index 180edbf7c..5bc27914a 100644 --- a/src/vector.h +++ b/src/vector.h @@ -44,6 +44,8 @@ GIT_INLINE(const void *) git_vector_get_const(const git_vector *v, unsigned int return (position < v->length) ? v->contents[position] : NULL; } +#define GIT_VECTOR_GET(V,I) ((I) < (V)->length ? (V)->contents[(I)] : NULL) + GIT_INLINE(void *) git_vector_last(git_vector *v) { return (v->length > 0) ? git_vector_get(v, v->length - 1) : NULL; diff --git a/src/win32/dir.c b/src/win32/dir.c index 035e2b685..bc3d40fa5 100644 --- a/src/win32/dir.c +++ b/src/win32/dir.c @@ -27,8 +27,8 @@ static int init_filter(char *filter, size_t n, const char *dir) git__DIR *git__opendir(const char *dir) { char filter[4096]; - wchar_t* filter_w; - git__DIR *new; + wchar_t* filter_w = NULL; + git__DIR *new = NULL; if (!dir || !init_filter(filter, sizeof(filter), dir)) return NULL; @@ -37,25 +37,29 @@ git__DIR *git__opendir(const char *dir) if (!new) return NULL; - new->dir = git__malloc(strlen(dir)+1); - if (!new->dir) { - git__free(new); - return NULL; - } - strcpy(new->dir, dir); + new->dir = git__strdup(dir); + if (!new->dir) + goto fail; filter_w = gitwin_to_utf16(filter); + if (!filter_w) + goto fail; + new->h = FindFirstFileW(filter_w, &new->f); git__free(filter_w); if (new->h == INVALID_HANDLE_VALUE) { - git__free(new->dir); - git__free(new); - return NULL; + giterr_set(GITERR_OS, "Could not open directory '%s'", dir); + goto fail; } - new->first = 1; + new->first = 1; return new; + +fail: + git__free(new->dir); + git__free(new); + return NULL; } int git__readdir_ext( @@ -67,22 +71,32 @@ int git__readdir_ext( if (!d || !entry || !result || d->h == INVALID_HANDLE_VALUE) return -1; + *result = NULL; + if (d->first) d->first = 0; else if (!FindNextFileW(d->h, &d->f)) { - *result = NULL; - return 0; + if (GetLastError() == ERROR_NO_MORE_FILES) + return 0; + giterr_set(GITERR_OS, "Could not read from directory '%s'", d->dir); + return -1; } if (wcslen(d->f.cFileName) >= sizeof(entry->d_name)) return -1; entry->d_ino = 0; - WideCharToMultiByte( + + if (WideCharToMultiByte( gitwin_get_codepage(), 0, d->f.cFileName, -1, - entry->d_name, GIT_PATH_MAX, NULL, NULL); + entry->d_name, GIT_PATH_MAX, NULL, NULL) == 0) + { + giterr_set(GITERR_OS, "Could not convert filename to UTF-8"); + return -1; + } *result = entry; + if (is_dir != NULL) *is_dir = ((d->f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0); @@ -102,32 +116,40 @@ void git__rewinddir(git__DIR *d) char filter[4096]; wchar_t* filter_w; - if (d) { - if (d->h != INVALID_HANDLE_VALUE) - FindClose(d->h); + if (!d) + return; + + if (d->h != INVALID_HANDLE_VALUE) { + FindClose(d->h); d->h = INVALID_HANDLE_VALUE; d->first = 0; + } - if (init_filter(filter, sizeof(filter), d->dir)) { - filter_w = gitwin_to_utf16(filter); - d->h = FindFirstFileW(filter_w, &d->f); - git__free(filter_w); + if (!init_filter(filter, sizeof(filter), d->dir) || + (filter_w = gitwin_to_utf16(filter)) == NULL) + return; - if (d->h != INVALID_HANDLE_VALUE) - d->first = 1; - } - } + d->h = FindFirstFileW(filter_w, &d->f); + git__free(filter_w); + + if (d->h == INVALID_HANDLE_VALUE) + giterr_set(GITERR_OS, "Could not open directory '%s'", d->dir); + else + d->first = 1; } int git__closedir(git__DIR *d) { - if (d) { - if (d->h != INVALID_HANDLE_VALUE) - FindClose(d->h); - if (d->dir) - git__free(d->dir); - git__free(d); + if (!d) + return 0; + + if (d->h != INVALID_HANDLE_VALUE) { + FindClose(d->h); + d->h = INVALID_HANDLE_VALUE; } + git__free(d->dir); + d->dir = NULL; + git__free(d); return 0; } diff --git a/src/win32/map.c b/src/win32/map.c index 60adf0f94..f730120cc 100644 --- a/src/win32/map.c +++ b/src/win32/map.c @@ -33,12 +33,7 @@ int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offs git_off_t page_start; git_off_t page_offset; - assert((out != NULL) && (len > 0)); - - if ((out == NULL) || (len == 0)) { - errno = EINVAL; - return git__throw(GIT_ERROR, "Failed to mmap. No map or zero length"); - } + GIT_MMAP_VALIDATE(out, len, prot, flags); out->data = NULL; out->len = 0; @@ -46,86 +41,75 @@ int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offs if (fh == INVALID_HANDLE_VALUE) { errno = EBADF; - return git__throw(GIT_ERROR, "Failed to mmap. Invalid handle value"); + giterr_set(GITERR_OS, "Failed to mmap. Invalid handle value"); + return -1; } if (prot & GIT_PROT_WRITE) fmap_prot |= PAGE_READWRITE; else if (prot & GIT_PROT_READ) fmap_prot |= PAGE_READONLY; - else { - errno = EINVAL; - return git__throw(GIT_ERROR, "Failed to mmap. Invalid protection parameters"); - } if (prot & GIT_PROT_WRITE) view_prot |= FILE_MAP_WRITE; if (prot & GIT_PROT_READ) view_prot |= FILE_MAP_READ; - if (flags & GIT_MAP_FIXED) { - errno = EINVAL; - return git__throw(GIT_ERROR, "Failed to mmap. FIXED not set"); - } - page_start = (offset / page_size) * page_size; page_offset = offset - page_start; if (page_offset != 0) { /* offset must be multiple of page size */ errno = EINVAL; - return git__throw(GIT_ERROR, "Failed to mmap. Offset must be multiple of page size"); + giterr_set(GITERR_OS, "Failed to mmap. Offset must be multiple of page size"); + return -1; } out->fmh = CreateFileMapping(fh, NULL, fmap_prot, 0, 0, NULL); if (!out->fmh || out->fmh == INVALID_HANDLE_VALUE) { - /* errno = ? */ + giterr_set(GITERR_OS, "Failed to mmap. Invalid handle value"); out->fmh = NULL; - return git__throw(GIT_ERROR, "Failed to mmap. Invalid handle value"); + return -1; } assert(sizeof(git_off_t) == 8); + off_low = (DWORD)(page_start); off_hi = (DWORD)(page_start >> 32); out->data = MapViewOfFile(out->fmh, view_prot, off_hi, off_low, len); if (!out->data) { - /* errno = ? */ + giterr_set(GITERR_OS, "Failed to mmap. No data written"); CloseHandle(out->fmh); out->fmh = NULL; - return git__throw(GIT_ERROR, "Failed to mmap. No data written"); + return -1; } out->len = len; - return GIT_SUCCESS; + return 0; } int p_munmap(git_map *map) { - assert(map != NULL); + int error = 0; - if (!map) - return git__throw(GIT_ERROR, "Failed to munmap. Map does not exist"); + assert(map != NULL); if (map->data) { if (!UnmapViewOfFile(map->data)) { - /* errno = ? */ - CloseHandle(map->fmh); - map->data = NULL; - map->fmh = NULL; - return git__throw(GIT_ERROR, "Failed to munmap. Could not unmap view of file"); + giterr_set(GITERR_OS, "Failed to munmap. Could not unmap view of file"); + error = -1; } map->data = NULL; } if (map->fmh) { if (!CloseHandle(map->fmh)) { - /* errno = ? */ - map->fmh = NULL; - return git__throw(GIT_ERROR, "Failed to munmap. Could not close handle"); + giterr_set(GITERR_OS, "Failed to munmap. Could not close handle"); + error = -1; } map->fmh = NULL; } - return GIT_SUCCESS; + return error; } diff --git a/src/win32/posix.h b/src/win32/posix.h index 60adc9666..d13d3e39b 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -49,5 +49,7 @@ extern int p_open(const char *path, int flags); extern int p_creat(const char *path, mode_t mode); extern int p_getcwd(char *buffer_out, size_t size); extern int p_rename(const char *from, const char *to); +extern int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags); +extern int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags); #endif diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 91585aeae..8af664165 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -4,7 +4,7 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#include "posix.h" +#include "../posix.h" #include "path.h" #include "utf-conv.h" #include <errno.h> @@ -17,10 +17,11 @@ int p_unlink(const char *path) int ret = 0; wchar_t* buf; - buf = gitwin_to_utf16(path); - _wchmod(buf, 0666); - ret = _wunlink(buf); - git__free(buf); + if ((buf = gitwin_to_utf16(path)) != NULL) { + _wchmod(buf, 0666); + ret = _wunlink(buf); + git__free(buf); + } return ret; } @@ -60,6 +61,8 @@ static int do_lstat(const char *file_name, struct stat *buf) { WIN32_FILE_ATTRIBUTE_DATA fdata; wchar_t* fbuf = gitwin_to_utf16(file_name); + if (!fbuf) + return -1; if (GetFileAttributesExW(fbuf, GetFileExInfoStandard, &fdata)) { int fMode = S_IREAD; @@ -87,54 +90,43 @@ static int do_lstat(const char *file_name, struct stat *buf) buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime)); git__free(fbuf); - return GIT_SUCCESS; + return 0; } git__free(fbuf); - - switch (GetLastError()) { - case ERROR_ACCESS_DENIED: - case ERROR_SHARING_VIOLATION: - case ERROR_LOCK_VIOLATION: - case ERROR_SHARING_BUFFER_EXCEEDED: - return GIT_EOSERR; - - case ERROR_BUFFER_OVERFLOW: - case ERROR_NOT_ENOUGH_MEMORY: - return GIT_ENOMEM; - - default: - return GIT_EINVALIDPATH; - } + return -1; } int p_lstat(const char *file_name, struct stat *buf) { - int namelen, error; - char alt_name[GIT_PATH_MAX]; + int error; + size_t namelen; + char *alt_name; - if ((error = do_lstat(file_name, buf)) == GIT_SUCCESS) - return GIT_SUCCESS; + if (do_lstat(file_name, buf) == 0) + return 0; /* if file_name ended in a '/', Windows returned ENOENT; * try again without trailing slashes */ - if (error != GIT_EINVALIDPATH) - return git__throw(GIT_EOSERR, "Failed to lstat file"); - namelen = strlen(file_name); if (namelen && file_name[namelen-1] != '/') - return git__throw(GIT_EOSERR, "Failed to lstat file"); + return -1; while (namelen && file_name[namelen-1] == '/') --namelen; - if (!namelen || namelen >= GIT_PATH_MAX) - return git__throw(GIT_ENOMEM, "Failed to lstat file"); + if (!namelen) + return -1; + + alt_name = git__strndup(file_name, namelen); + if (!alt_name) + return -1; - memcpy(alt_name, file_name, namelen); - alt_name[namelen] = 0; - return do_lstat(alt_name, buf); + error = do_lstat(alt_name, buf); + + git__free(alt_name); + return error; } int p_readlink(const char *link, char *target, size_t target_len) @@ -145,6 +137,9 @@ int p_readlink(const char *link, char *target, size_t target_len) DWORD dwRet; wchar_t* link_w; wchar_t* target_w; + int error = 0; + + assert(link && target && target_len > 0); /* * Try to load the pointer to pGetFinalPath dynamically, because @@ -156,12 +151,15 @@ int p_readlink(const char *link, char *target, size_t target_len) if (library != NULL) pGetFinalPath = (fpath_func)GetProcAddress(library, "GetFinalPathNameByHandleW"); - if (pGetFinalPath == NULL) - return git__throw(GIT_EOSERR, + if (pGetFinalPath == NULL) { + giterr_set(GITERR_OS, "'GetFinalPathNameByHandleW' is not available in this platform"); + return -1; + } } link_w = gitwin_to_utf16(link); + GITERR_CHECK_ALLOC(link_w); hFile = CreateFileW(link_w, // file to open GENERIC_READ, // open for reading @@ -173,50 +171,49 @@ int p_readlink(const char *link, char *target, size_t target_len) git__free(link_w); - if (hFile == INVALID_HANDLE_VALUE) - return GIT_EOSERR; - - if (target_len <= 0) { - return GIT_EINVALIDARGS; + if (hFile == INVALID_HANDLE_VALUE) { + giterr_set(GITERR_OS, "Cannot open '%s' for reading", link); + return -1; } target_w = (wchar_t*)git__malloc(target_len * sizeof(wchar_t)); + GITERR_CHECK_ALLOC(target_w); - dwRet = pGetFinalPath(hFile, target_w, target_len, 0x0); - if (dwRet >= target_len) { - git__free(target_w); - CloseHandle(hFile); - return GIT_ENOMEM; - } - - if (!WideCharToMultiByte(CP_UTF8, 0, target_w, -1, target, target_len * sizeof(char), NULL, NULL)) { - git__free(target_w); - return GIT_EOSERR; - } + dwRet = pGetFinalPath(hFile, target_w, (DWORD)target_len, 0x0); + if (dwRet == 0 || + dwRet >= target_len || + !WideCharToMultiByte(CP_UTF8, 0, target_w, -1, target, + (int)(target_len * sizeof(char)), NULL, NULL)) + error = -1; git__free(target_w); CloseHandle(hFile); - if (dwRet > 4) { - /* Skip first 4 characters if they are "\\?\" */ - if (target[0] == '\\' && target[1] == '\\' && target[2] == '?' && target[3] == '\\') { - char tmp[GIT_PATH_MAX]; - unsigned int offset = 4; - dwRet -= 4; - - /* \??\UNC\ */ - if (dwRet > 7 && target[4] == 'U' && target[5] == 'N' && target[6] == 'C') { - offset += 2; - dwRet -= 2; - target[offset] = '\\'; - } - - memcpy(tmp, target + offset, dwRet); - memcpy(target, tmp, dwRet); + if (error) + return error; + + /* Skip first 4 characters if they are "\\?\" */ + if (dwRet > 4 && + target[0] == '\\' && target[1] == '\\' && + target[2] == '?' && target[3] == '\\') + { + unsigned int offset = 4; + dwRet -= 4; + + /* \??\UNC\ */ + if (dwRet > 7 && + target[4] == 'U' && target[5] == 'N' && target[6] == 'C') + { + offset += 2; + dwRet -= 2; + target[offset] = '\\'; } + + memmove(target, target + offset, dwRet); } target[dwRet] = '\0'; + return dwRet; } @@ -224,8 +221,9 @@ int p_open(const char *path, int flags) { int fd; wchar_t* buf = gitwin_to_utf16(path); + if (!buf) + return -1; fd = _wopen(buf, flags | _O_BINARY); - git__free(buf); return fd; } @@ -234,24 +232,31 @@ int p_creat(const char *path, mode_t mode) { int fd; wchar_t* buf = gitwin_to_utf16(path); + if (!buf) + return -1; fd = _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY, mode); - git__free(buf); return fd; } int p_getcwd(char *buffer_out, size_t size) { - wchar_t* buf = (wchar_t*)git__malloc(sizeof(wchar_t) * (int)size); + int ret; + wchar_t* buf; + + if ((size_t)((int)size) != size) + return -1; + + buf = (wchar_t*)git__malloc(sizeof(wchar_t) * (int)size); + GITERR_CHECK_ALLOC(buf); + _wgetcwd(buf, (int)size); - if (!WideCharToMultiByte(CP_UTF8, 0, buf, -1, buffer_out, size, NULL, NULL)) { - git__free(buf); - return GIT_EOSERR; - } + ret = WideCharToMultiByte( + CP_UTF8, 0, buf, -1, buffer_out, (int)size, NULL, NULL); git__free(buf); - return GIT_SUCCESS; + return !ret ? -1 : 0; } int p_stat(const char* path, struct stat* buf) @@ -262,8 +267,10 @@ int p_stat(const char* path, struct stat* buf) int p_chdir(const char* path) { wchar_t* buf = gitwin_to_utf16(path); - int ret = _wchdir(buf); - + int ret; + if (!buf) + return -1; + ret = _wchdir(buf); git__free(buf); return ret; } @@ -271,8 +278,10 @@ int p_chdir(const char* path) int p_chmod(const char* path, mode_t mode) { wchar_t* buf = gitwin_to_utf16(path); - int ret = _wchmod(buf, mode); - + int ret; + if (!buf) + return -1; + ret = _wchmod(buf, mode); git__free(buf); return ret; } @@ -280,26 +289,25 @@ int p_chmod(const char* path, mode_t mode) int p_rmdir(const char* path) { wchar_t* buf = gitwin_to_utf16(path); - int ret = _wrmdir(buf); - + int ret; + if (!buf) + return -1; + ret = _wrmdir(buf); git__free(buf); return ret; } int p_hide_directory__w32(const char *path) { - int error; + int res; wchar_t* buf = gitwin_to_utf16(path); + if (!buf) + return -1; - error = SetFileAttributesW(buf, FILE_ATTRIBUTE_HIDDEN) != 0 ? - GIT_SUCCESS : GIT_ERROR; /* MSDN states a "non zero" value indicates a success */ - + res = SetFileAttributesW(buf, FILE_ATTRIBUTE_HIDDEN); git__free(buf); - if (error < GIT_SUCCESS) - error = git__throw(GIT_EOSERR, "Failed to hide directory '%s'", path); - - return error; + return (res != 0) ? 0 : -1; /* MSDN states a "non zero" value indicates a success */ } char *p_realpath(const char *orig_path, char *buffer) @@ -308,6 +316,9 @@ char *p_realpath(const char *orig_path, char *buffer) wchar_t* orig_path_w = gitwin_to_utf16(orig_path); wchar_t* buffer_w = (wchar_t*)git__malloc(GIT_PATH_MAX * sizeof(wchar_t)); + if (!orig_path_w || !buffer_w) + return NULL; + ret = GetFullPathNameW(orig_path_w, GIT_PATH_MAX, buffer_w, NULL); git__free(orig_path_w); @@ -341,8 +352,12 @@ done: int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr) { #ifdef _MSC_VER - int len = _vsnprintf(buffer, count, format, argptr); - return (len < 0) ? _vscprintf(format, argptr) : len; + int len; + + if (count == 0 || (len = _vsnprintf(buffer, count, format, argptr)) < 0) + return _vscprintf(format, argptr); + + return len; #else /* MinGW */ return vsnprintf(buffer, count, format, argptr); #endif @@ -366,10 +381,10 @@ int p_mkstemp(char *tmp_path) { #if defined(_MSC_VER) if (_mktemp_s(tmp_path, strlen(tmp_path) + 1) != 0) - return GIT_EOSERR; + return -1; #else if (_mktemp(tmp_path) == NULL) - return GIT_EOSERR; + return -1; #endif return p_creat(tmp_path, 0744); @@ -378,15 +393,17 @@ int p_mkstemp(char *tmp_path) int p_setenv(const char* name, const char* value, int overwrite) { if (overwrite != 1) - return EINVAL; + return -1; - return (SetEnvironmentVariableA(name, value) == 0 ? GIT_EOSERR : GIT_SUCCESS); + return (SetEnvironmentVariableA(name, value) == 0 ? -1 : 0); } int p_access(const char* path, mode_t mode) { wchar_t *buf = gitwin_to_utf16(path); int ret; + if (!buf) + return -1; ret = _waccess(buf, mode); git__free(buf); @@ -394,16 +411,35 @@ int p_access(const char* path, mode_t mode) return ret; } -extern int p_rename(const char *from, const char *to) +int p_rename(const char *from, const char *to) { wchar_t *wfrom = gitwin_to_utf16(from); wchar_t *wto = gitwin_to_utf16(to); int ret; - ret = MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) ? GIT_SUCCESS : GIT_EOSERR; + if (!wfrom || !wto) + return -1; + + ret = MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) ? 0 : -1; git__free(wfrom); git__free(wto); return ret; } + +int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags) +{ + if ((size_t)((int)length) != length) + return -1; /* giterr_set will be done by caller */ + + return recv(socket, buffer, (int)length, flags); +} + +int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags) +{ + if ((size_t)((int)length) != length) + return -1; /* giterr_set will be done by caller */ + + return send(socket, buffer, (int)length, flags); +} diff --git a/src/win32/pthread.c b/src/win32/pthread.c index 3db536848..3a186c8d9 100644 --- a/src/win32/pthread.c +++ b/src/win32/pthread.c @@ -7,13 +7,16 @@ #include "pthread.h" -int pthread_create(pthread_t *GIT_RESTRICT thread, - const pthread_attr_t *GIT_RESTRICT attr, - void *(*start_routine)(void*), void *GIT_RESTRICT arg) +int pthread_create( + pthread_t *GIT_RESTRICT thread, + const pthread_attr_t *GIT_RESTRICT attr, + void *(*start_routine)(void*), + void *GIT_RESTRICT arg) { GIT_UNUSED(attr); - *thread = (pthread_t) CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)start_routine, arg, 0, NULL); - return *thread ? GIT_SUCCESS : git__throw(GIT_EOSERR, "Failed to create pthread"); + *thread = (pthread_t) CreateThread( + NULL, 0, (LPTHREAD_START_ROUTINE)start_routine, arg, 0, NULL); + return *thread ? 0 : -1; } int pthread_join(pthread_t thread, void **value_ptr) diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c index 3c8be81d1..fbcb69d0a 100644 --- a/src/win32/utf-conv.c +++ b/src/win32/utf-conv.c @@ -31,25 +31,24 @@ void gitwin_set_utf8(void) wchar_t* gitwin_to_utf16(const char* str) { wchar_t* ret; - int cb; + size_t cb; - if (!str) { + if (!str) return NULL; - } cb = strlen(str) * sizeof(wchar_t); - if (cb == 0) { - ret = (wchar_t*)git__malloc(sizeof(wchar_t)); - ret[0] = 0; - return ret; - } + if (cb == 0) + return (wchar_t *)git__calloc(1, sizeof(wchar_t)); /* Add space for null terminator */ cb += sizeof(wchar_t); - ret = (wchar_t*)git__malloc(cb); + ret = (wchar_t *)git__malloc(cb); + if (!ret) + return NULL; - if (MultiByteToWideChar(_active_codepage, 0, str, -1, ret, cb) == 0) { + if (MultiByteToWideChar(_active_codepage, 0, str, -1, ret, (int)cb) == 0) { + giterr_set(GITERR_OS, "Could not convert string to UTF-16"); git__free(ret); ret = NULL; } @@ -59,31 +58,33 @@ wchar_t* gitwin_to_utf16(const char* str) int gitwin_append_utf16(wchar_t *buffer, const char *str, size_t len) { - return MultiByteToWideChar(_active_codepage, 0, str, -1, buffer, len); + int result = MultiByteToWideChar(_active_codepage, 0, str, -1, buffer, (int)len); + if (result == 0) + giterr_set(GITERR_OS, "Could not convert string to UTF-16"); + return result; } char* gitwin_from_utf16(const wchar_t* str) { char* ret; - int cb; + size_t cb; - if (!str) { + if (!str) return NULL; - } cb = wcslen(str) * sizeof(char); - if (cb == 0) { - ret = (char*)git__malloc(sizeof(char)); - ret[0] = 0; - return ret; - } + if (cb == 0) + return (char *)git__calloc(1, sizeof(char)); /* Add space for null terminator */ cb += sizeof(char); ret = (char*)git__malloc(cb); + if (!ret) + return NULL; - if (WideCharToMultiByte(_active_codepage, 0, str, -1, ret, cb, NULL, NULL) == 0) { + if (WideCharToMultiByte(_active_codepage, 0, str, -1, ret, (int)cb, NULL, NULL) == 0) { + giterr_set(GITERR_OS, "Could not convert string to UTF-8"); git__free(ret); ret = NULL; } diff --git a/src/xdiff/xemit.c b/src/xdiff/xemit.c index 8b7d417a1..e3e63d902 100644 --- a/src/xdiff/xemit.c +++ b/src/xdiff/xemit.c @@ -40,7 +40,7 @@ static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec) { static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t *ecb) { - long size, psize = strlen(pre); + long size, psize = (long)strlen(pre); char const *rec; size = xdl_get_rec(xdf, ri, &rec); diff --git a/src/xdiff/xmerge.c b/src/xdiff/xmerge.c index 9e13b25ab..84e424672 100644 --- a/src/xdiff/xmerge.c +++ b/src/xdiff/xmerge.c @@ -149,9 +149,9 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, int size, int i, int style, xdmerge_t *m, char *dest, int marker_size) { - int marker1_size = (name1 ? strlen(name1) + 1 : 0); - int marker2_size = (name2 ? strlen(name2) + 1 : 0); - int marker3_size = (name3 ? strlen(name3) + 1 : 0); + int marker1_size = (name1 ? (int)strlen(name1) + 1 : 0); + int marker2_size = (name2 ? (int)strlen(name2) + 1 : 0); + int marker3_size = (name3 ? (int)strlen(name3) + 1 : 0); if (marker_size <= 0) marker_size = DEFAULT_CONFLICT_MARKER_SIZE; diff --git a/src/xdiff/xutils.c b/src/xdiff/xutils.c index 9dea04bba..bb7bdee49 100644 --- a/src/xdiff/xutils.c +++ b/src/xdiff/xutils.c @@ -62,14 +62,14 @@ int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize, void *xdl_mmfile_first(mmfile_t *mmf, long *size) { - *size = mmf->size; + *size = (long)mmf->size; return mmf->ptr; } long xdl_mmfile_size(mmfile_t *mmf) { - return mmf->size; + return (long)mmf->size; } @@ -321,7 +321,7 @@ int xdl_num_out(char *out, long val) { *str++ = '0'; *str = '\0'; - return str - out; + return (int)(str - out); } |