diff options
author | Chris Young <chris@unsatisfactorysoftware.co.uk> | 2012-06-07 20:29:22 +0100 |
---|---|---|
committer | Chris Young <chris@unsatisfactorysoftware.co.uk> | 2012-06-07 20:29:22 +0100 |
commit | c3f35902f3f951de5ce5193409f336ee45c682b6 (patch) | |
tree | e7329cc1496e676a65fb108bb9e830437e5ced7f /src | |
parent | cada414a8044307b28f7a4c75986e5473bb4bc1c (diff) | |
parent | cddb8efe564738873a4cf9ac63b7976d74035ae9 (diff) | |
download | libgit2-c3f35902f3f951de5ce5193409f336ee45c682b6.tar.gz |
Merge remote-tracking branch 'source/development' into update-test
Merging main libgit2!
Conflicts:
CMakeLists.txt
src/unix/map.c
Diffstat (limited to 'src')
-rw-r--r-- | src/attr.c | 677 | ||||
-rw-r--r-- | src/attr.h | 56 | ||||
-rw-r--r-- | src/attr_file.c | 609 | ||||
-rw-r--r-- | src/attr_file.h | 145 | ||||
-rw-r--r-- | src/backends/hiredis.c | 217 | ||||
-rw-r--r-- | src/backends/sqlite.c | 296 | ||||
-rw-r--r-- | src/blob.c | 289 | ||||
-rw-r--r-- | src/blob.h | 7 | ||||
-rw-r--r-- | src/branch.c | 208 | ||||
-rw-r--r-- | src/branch.h | 17 | ||||
-rw-r--r-- | src/bswap.h | 12 | ||||
-rw-r--r-- | src/buffer.c | 461 | ||||
-rw-r--r-- | src/buffer.h | 135 | ||||
-rw-r--r-- | src/cache.c | 116 | ||||
-rw-r--r-- | src/cache.h | 28 | ||||
-rw-r--r-- | src/cc-compat.h | 82 | ||||
-rw-r--r-- | src/commit.c | 337 | ||||
-rw-r--r-- | src/commit.h | 9 | ||||
-rw-r--r-- | src/common.h | 52 | ||||
-rw-r--r-- | src/compat/fnmatch.c | 180 | ||||
-rw-r--r-- | src/compat/fnmatch.h | 27 | ||||
-rw-r--r-- | src/config.c | 511 | ||||
-rw-r--r-- | src/config.h | 19 | ||||
-rw-r--r-- | src/config_cache.c | 94 | ||||
-rw-r--r-- | src/config_file.c | 1171 | ||||
-rw-r--r-- | src/config_file.h | 31 | ||||
-rw-r--r-- | src/crlf.c | 228 | ||||
-rw-r--r-- | src/delta-apply.c | 44 | ||||
-rw-r--r-- | src/delta-apply.h | 8 | ||||
-rw-r--r-- | src/diff.c | 784 | ||||
-rw-r--r-- | src/diff.h | 40 | ||||
-rw-r--r-- | src/diff_output.c | 786 | ||||
-rw-r--r-- | src/dir.h | 41 | ||||
-rw-r--r-- | src/errors.c | 180 | ||||
-rw-r--r-- | src/fetch.c | 200 | ||||
-rw-r--r-- | src/fetch.h | 19 | ||||
-rw-r--r-- | src/filebuf.c | 389 | ||||
-rw-r--r-- | src/filebuf.h | 48 | ||||
-rw-r--r-- | src/fileops.c | 728 | ||||
-rw-r--r-- | src/fileops.h | 287 | ||||
-rw-r--r-- | src/filter.c | 165 | ||||
-rw-r--r-- | src/filter.h | 119 | ||||
-rw-r--r-- | src/global.c | 134 | ||||
-rw-r--r-- | src/global.h | 27 | ||||
-rw-r--r-- | src/hash.c | 30 | ||||
-rw-r--r-- | src/hash.h | 5 | ||||
-rw-r--r-- | src/hashtable.c | 261 | ||||
-rw-r--r-- | src/hashtable.h | 73 | ||||
-rw-r--r-- | src/ignore.c | 203 | ||||
-rw-r--r-- | src/ignore.h | 38 | ||||
-rw-r--r-- | src/index.c | 776 | ||||
-rw-r--r-- | src/index.h | 30 | ||||
-rw-r--r-- | src/indexer.c | 898 | ||||
-rw-r--r-- | src/iterator.c | 748 | ||||
-rw-r--r-- | src/iterator.h | 151 | ||||
-rw-r--r-- | src/khash.h | 608 | ||||
-rw-r--r-- | src/map.h | 39 | ||||
-rw-r--r-- | src/message.c | 61 | ||||
-rw-r--r-- | src/message.h | 14 | ||||
-rw-r--r-- | src/mingw-compat.h | 13 | ||||
-rw-r--r-- | src/msvc-compat.h | 46 | ||||
-rw-r--r-- | src/mwindow.c | 270 | ||||
-rw-r--r-- | src/mwindow.h | 45 | ||||
-rw-r--r-- | src/netops.c | 517 | ||||
-rw-r--r-- | src/netops.h | 39 | ||||
-rw-r--r-- | src/notes.c | 548 | ||||
-rw-r--r-- | src/notes.h | 28 | ||||
-rw-r--r-- | src/object.c | 140 | ||||
-rw-r--r-- | src/odb.c | 482 | ||||
-rw-r--r-- | src/odb.h | 57 | ||||
-rw-r--r-- | src/odb_loose.c | 591 | ||||
-rw-r--r-- | src/odb_pack.c | 1485 | ||||
-rw-r--r-- | src/oid.c | 232 | ||||
-rw-r--r-- | src/oid.h | 17 | ||||
-rw-r--r-- | src/oidmap.h | 42 | ||||
-rw-r--r-- | src/pack.c | 816 | ||||
-rw-r--r-- | src/pack.h | 106 | ||||
-rw-r--r-- | src/path.c | 656 | ||||
-rw-r--r-- | src/path.h | 278 | ||||
-rw-r--r-- | src/pkt.c | 355 | ||||
-rw-r--r-- | src/pkt.h | 81 | ||||
-rw-r--r-- | src/pool.c | 294 | ||||
-rw-r--r-- | src/pool.h | 125 | ||||
-rw-r--r-- | src/posix.c | 116 | ||||
-rw-r--r-- | src/posix.h | 79 | ||||
-rw-r--r-- | src/ppc/sha1.c | 10 | ||||
-rw-r--r-- | src/ppc/sha1.h | 5 | ||||
-rw-r--r-- | src/pqueue.c | 146 | ||||
-rw-r--r-- | src/pqueue.h | 30 | ||||
-rw-r--r-- | src/protocol.c | 58 | ||||
-rw-r--r-- | src/protocol.h | 23 | ||||
-rw-r--r-- | src/reflog.c | 340 | ||||
-rw-r--r-- | src/reflog.h | 34 | ||||
-rw-r--r-- | src/refs.c | 1998 | ||||
-rw-r--r-- | src/refs.h | 56 | ||||
-rw-r--r-- | src/refspec.c | 129 | ||||
-rw-r--r-- | src/refspec.h | 35 | ||||
-rw-r--r-- | src/remote.c | 524 | ||||
-rw-r--r-- | src/remote.h | 26 | ||||
-rw-r--r-- | src/repository.c | 1122 | ||||
-rw-r--r-- | src/repository.h | 108 | ||||
-rw-r--r-- | src/revwalk.c | 649 | ||||
-rw-r--r-- | src/sha1.c (renamed from src/block-sha1/sha1.c) | 43 | ||||
-rw-r--r-- | src/sha1.h (renamed from src/block-sha1/sha1.h) | 9 | ||||
-rw-r--r-- | src/sha1_lookup.c | 100 | ||||
-rw-r--r-- | src/sha1_lookup.h | 14 | ||||
-rw-r--r-- | src/signature.c | 326 | ||||
-rw-r--r-- | src/signature.h | 10 | ||||
-rw-r--r-- | src/status.c | 242 | ||||
-rw-r--r-- | src/strmap.h | 64 | ||||
-rw-r--r-- | src/submodule.c | 387 | ||||
-rw-r--r-- | src/t03-data.h | 344 | ||||
-rw-r--r-- | src/tag.c | 478 | ||||
-rw-r--r-- | src/tag.h | 7 | ||||
-rw-r--r-- | src/thread-utils.c | 22 | ||||
-rw-r--r-- | src/thread-utils.h | 28 | ||||
-rw-r--r-- | src/transport.c | 97 | ||||
-rw-r--r-- | src/transport.h | 130 | ||||
-rw-r--r-- | src/transports/git.c | 477 | ||||
-rw-r--r-- | src/transports/http.c | 707 | ||||
-rw-r--r-- | src/transports/local.c | 241 | ||||
-rw-r--r-- | src/tree-cache.c | 184 | ||||
-rw-r--r-- | src/tree-cache.h | 31 | ||||
-rw-r--r-- | src/tree.c | 725 | ||||
-rw-r--r-- | src/tree.h | 22 | ||||
-rw-r--r-- | src/tsort.c | 367 | ||||
-rw-r--r-- | src/unix/map.c | 44 | ||||
-rw-r--r-- | src/unix/posix.h | 31 | ||||
-rw-r--r-- | src/util.c | 413 | ||||
-rw-r--r-- | src/util.h | 234 | ||||
-rw-r--r-- | src/vector.c | 165 | ||||
-rw-r--r-- | src/vector.h | 48 | ||||
-rw-r--r-- | src/win32/dir.c | 131 | ||||
-rw-r--r-- | src/win32/dir.h | 42 | ||||
-rw-r--r-- | src/win32/fileops.c | 41 | ||||
-rw-r--r-- | src/win32/git2.rc | 42 | ||||
-rw-r--r-- | src/win32/map.c | 66 | ||||
-rw-r--r-- | src/win32/mingw-compat.h | 24 | ||||
-rw-r--r-- | src/win32/msvc-compat.h | 42 | ||||
-rw-r--r-- | src/win32/posix.h | 55 | ||||
-rw-r--r-- | src/win32/posix_w32.c | 472 | ||||
-rw-r--r-- | src/win32/pthread.c | 83 | ||||
-rw-r--r-- | src/win32/pthread.h | 30 | ||||
-rw-r--r-- | src/win32/utf-conv.c | 92 | ||||
-rw-r--r-- | src/win32/utf-conv.h | 18 | ||||
-rw-r--r-- | src/xdiff/xdiff.h | 135 | ||||
-rw-r--r-- | src/xdiff/xdiffi.c | 572 | ||||
-rw-r--r-- | src/xdiff/xdiffi.h | 63 | ||||
-rw-r--r-- | src/xdiff/xemit.c | 253 | ||||
-rw-r--r-- | src/xdiff/xemit.h | 36 | ||||
-rw-r--r-- | src/xdiff/xhistogram.c | 371 | ||||
-rw-r--r-- | src/xdiff/xinclude.h | 46 | ||||
-rw-r--r-- | src/xdiff/xmacros.h | 54 | ||||
-rw-r--r-- | src/xdiff/xmerge.c | 619 | ||||
-rw-r--r-- | src/xdiff/xpatience.c | 358 | ||||
-rw-r--r-- | src/xdiff/xprepare.c | 483 | ||||
-rw-r--r-- | src/xdiff/xprepare.h | 34 | ||||
-rw-r--r-- | src/xdiff/xtypes.h | 67 | ||||
-rw-r--r-- | src/xdiff/xutils.c | 419 | ||||
-rw-r--r-- | src/xdiff/xutils.h | 49 |
160 files changed, 28966 insertions, 8420 deletions
diff --git a/src/attr.c b/src/attr.c new file mode 100644 index 000000000..fb6651196 --- /dev/null +++ b/src/attr.c @@ -0,0 +1,677 @@ +#include "repository.h" +#include "fileops.h" +#include "config.h" +#include <ctype.h> + +GIT__USE_STRMAP; + +static int collect_attr_files( + git_repository *repo, + uint32_t flags, + const char *path, + git_vector *files); + + +int git_attr_get( + const char **value, + git_repository *repo, + uint32_t flags, + const char *pathname, + const char *name) +{ + int error; + git_attr_path path; + git_vector files = GIT_VECTOR_INIT; + unsigned int i, j; + git_attr_file *file; + git_attr_name attr; + git_attr_rule *rule; + + *value = NULL; + + if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0) + return -1; + + if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0) + goto cleanup; + + attr.name = name; + attr.name_hash = git_attr_file__name_hash(name); + + git_vector_foreach(&files, i, file) { + + git_attr_file__foreach_matching_rule(file, &path, j, rule) { + int pos = git_vector_bsearch(&rule->assigns, &attr); + if (pos >= 0) { + *value = ((git_attr_assignment *)git_vector_get( + &rule->assigns, pos))->value; + goto cleanup; + } + } + } + +cleanup: + git_vector_free(&files); + git_attr_path__free(&path); + + return error; +} + + +typedef struct { + git_attr_name name; + git_attr_assignment *found; +} attr_get_many_info; + +int git_attr_get_many( + const char **values, + git_repository *repo, + uint32_t flags, + const char *pathname, + size_t num_attr, + const char **names) +{ + int error; + git_attr_path path; + git_vector files = GIT_VECTOR_INIT; + unsigned int i, j, k; + git_attr_file *file; + git_attr_rule *rule; + attr_get_many_info *info = NULL; + size_t num_found = 0; + + memset((void *)values, 0, sizeof(const char *) * num_attr); + + if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0) + return -1; + + if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0) + goto cleanup; + + info = git__calloc(num_attr, sizeof(attr_get_many_info)); + GITERR_CHECK_ALLOC(info); + + git_vector_foreach(&files, i, file) { + + git_attr_file__foreach_matching_rule(file, &path, j, rule) { + + for (k = 0; k < num_attr; k++) { + int pos; + + if (info[k].found != NULL) /* already found assignment */ + continue; + + if (!info[k].name.name) { + info[k].name.name = names[k]; + info[k].name.name_hash = git_attr_file__name_hash(names[k]); + } + + pos = git_vector_bsearch(&rule->assigns, &info[k].name); + if (pos >= 0) { + info[k].found = (git_attr_assignment *) + git_vector_get(&rule->assigns, pos); + values[k] = info[k].found->value; + + if (++num_found == num_attr) + goto cleanup; + } + } + } + } + +cleanup: + git_vector_free(&files); + git_attr_path__free(&path); + git__free(info); + + return error; +} + + +int git_attr_foreach( + git_repository *repo, + uint32_t flags, + const char *pathname, + int (*callback)(const char *name, const char *value, void *payload), + void *payload) +{ + int error; + git_attr_path path; + git_vector files = GIT_VECTOR_INIT; + unsigned int i, j, k; + git_attr_file *file; + git_attr_rule *rule; + git_attr_assignment *assign; + git_strmap *seen = NULL; + + if (git_attr_path__init(&path, pathname, git_repository_workdir(repo)) < 0) + return -1; + + if ((error = collect_attr_files(repo, flags, pathname, &files)) < 0) + goto cleanup; + + seen = git_strmap_alloc(); + GITERR_CHECK_ALLOC(seen); + + git_vector_foreach(&files, i, file) { + + git_attr_file__foreach_matching_rule(file, &path, j, rule) { + + git_vector_foreach(&rule->assigns, k, assign) { + /* skip if higher priority assignment was already seen */ + if (git_strmap_exists(seen, assign->name)) + continue; + + git_strmap_insert(seen, assign->name, assign, error); + if (error >= 0) + error = callback(assign->name, assign->value, payload); + + if (error != 0) + goto cleanup; + } + } + } + +cleanup: + git_strmap_free(seen); + git_vector_free(&files); + git_attr_path__free(&path); + + return error; +} + + +int git_attr_add_macro( + git_repository *repo, + const char *name, + const char *values) +{ + int error; + git_attr_rule *macro = NULL; + git_pool *pool; + + if (git_attr_cache__init(repo) < 0) + return -1; + + macro = git__calloc(1, sizeof(git_attr_rule)); + GITERR_CHECK_ALLOC(macro); + + 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, pool, ¯o->assigns, &values); + + if (!error) + error = git_attr_cache__insert_macro(repo, macro); + + if (error < 0) + git_attr_rule__free(macro); + + return error; +} + +bool git_attr_cache__is_cached( + git_repository *repo, git_attr_file_source source, const char *path) +{ + git_buf cache_key = GIT_BUF_INIT; + git_strmap *files = git_repository_attr_cache(repo)->files; + const char *workdir = git_repository_workdir(repo); + bool rval; + + if (workdir && git__prefixcmp(path, workdir) == 0) + path += strlen(workdir); + if (git_buf_printf(&cache_key, "%d#%s", (int)source, path) < 0) + return false; + + rval = git_strmap_exists(files, git_buf_cstr(&cache_key)); + + git_buf_free(&cache_key); + + return rval; +} + +static int load_attr_file( + const char **data, + git_attr_file_stat_sig *sig, + const char *filename) +{ + int error; + git_buf content = GIT_BUF_INIT; + struct stat st; + + if (p_stat(filename, &st) < 0) + return GIT_ENOTFOUND; + + if (sig != NULL && + (git_time_t)st.st_mtime == sig->seconds && + (git_off_t)st.st_size == sig->size && + (unsigned int)st.st_ino == sig->ino) + return GIT_ENOTFOUND; + + error = git_futils_readbuffer_updated(&content, filename, NULL, NULL); + if (error < 0) + return error; + + if (sig != NULL) { + sig->seconds = (git_time_t)st.st_mtime; + sig->size = (git_off_t)st.st_size; + sig->ino = (unsigned int)st.st_ino; + } + + *data = git_buf_detach(&content); + + return 0; +} + +static int load_attr_blob_from_index( + const char **content, + git_blob **blob, + git_repository *repo, + const git_oid *old_oid, + const char *relfile) +{ + int error; + git_index *index; + git_index_entry *entry; + + if ((error = git_repository_index__weakptr(&index, repo)) < 0 || + (error = git_index_find(index, relfile)) < 0) + return error; + + entry = git_index_get(index, error); + + if (old_oid && git_oid_cmp(old_oid, &entry->oid) == 0) + return GIT_ENOTFOUND; + + if ((error = git_blob_lookup(blob, repo, &entry->oid)) < 0) + return error; + + *content = git_blob_rawcontent(*blob); + return 0; +} + +static int load_attr_from_cache( + git_attr_file **file, + git_attr_cache *cache, + git_attr_file_source source, + const char *relative_path) +{ + git_buf cache_key = GIT_BUF_INIT; + khiter_t cache_pos; + + *file = NULL; + + if (!cache || !cache->files) + return 0; + + if (git_buf_printf(&cache_key, "%d#%s", (int)source, relative_path) < 0) + return -1; + + cache_pos = git_strmap_lookup_index(cache->files, cache_key.ptr); + + git_buf_free(&cache_key); + + if (git_strmap_valid_index(cache->files, cache_pos)) + *file = git_strmap_value_at(cache->files, cache_pos); + + return 0; +} + +int git_attr_cache__internal_file( + git_repository *repo, + const char *filename, + git_attr_file **file) +{ + int error = 0; + git_attr_cache *cache = git_repository_attr_cache(repo); + khiter_t cache_pos = git_strmap_lookup_index(cache->files, filename); + + if (git_strmap_valid_index(cache->files, cache_pos)) { + *file = git_strmap_value_at(cache->files, cache_pos); + return 0; + } + + if (git_attr_file__new(file, 0, filename, &cache->pool) < 0) + return -1; + + git_strmap_insert(cache->files, (*file)->key + 2, *file, error); + if (error > 0) + error = 0; + + return error; +} + +int git_attr_cache__push_file( + git_repository *repo, + const char *base, + const char *filename, + git_attr_file_source source, + git_attr_file_parser parse, + git_vector *stack) +{ + int error = 0; + git_buf path = GIT_BUF_INIT; + const char *workdir = git_repository_workdir(repo); + const char *relfile, *content = NULL; + git_attr_cache *cache = git_repository_attr_cache(repo); + git_attr_file *file = NULL; + git_blob *blob = NULL; + git_attr_file_stat_sig st; + + assert(filename && stack); + + /* join base and path as needed */ + if (base != NULL && git_path_root(filename) < 0) { + if (git_buf_joinpath(&path, base, filename) < 0) + return -1; + filename = path.ptr; + } + + relfile = filename; + if (workdir && git__prefixcmp(relfile, workdir) == 0) + relfile += strlen(workdir); + + /* check cache */ + if (load_attr_from_cache(&file, cache, source, relfile) < 0) + return -1; + + /* if not in cache, load data, parse, and cache */ + + if (source == GIT_ATTR_FILE_FROM_FILE) { + if (file) + memcpy(&st, &file->cache_data.st, sizeof(st)); + else + memset(&st, 0, sizeof(st)); + + error = load_attr_file(&content, &st, filename); + } else { + error = load_attr_blob_from_index(&content, &blob, + repo, file ? &file->cache_data.oid : NULL, relfile); + } + + if (error) { + /* not finding a file is not an error for this function */ + if (error == GIT_ENOTFOUND) { + giterr_clear(); + error = 0; + } + goto finish; + } + + /* if we got here, we have to parse and/or reparse the file */ + if (file) + git_attr_file__clear_rules(file); + else { + error = git_attr_file__new(&file, source, relfile, &cache->pool); + if (error < 0) + goto finish; + } + + if (parse && (error = parse(repo, content, file)) < 0) + goto finish; + + git_strmap_insert(cache->files, file->key, file, error); + if (error > 0) + error = 0; + + /* remember "cache buster" file signature */ + if (blob) + git_oid_cpy(&file->cache_data.oid, git_object_id((git_object *)blob)); + else + memcpy(&file->cache_data.st, &st, sizeof(st)); + +finish: + /* push file onto vector if we found one*/ + if (!error && file != NULL) + error = git_vector_insert(stack, file); + + if (error != 0) + git_attr_file__free(file); + + if (blob) + git_blob_free(blob); + else + git__free((void *)content); + + git_buf_free(&path); + + return error; +} + +#define push_attr_file(R,S,B,F) \ + git_attr_cache__push_file((R),(B),(F),GIT_ATTR_FILE_FROM_FILE,git_attr_file__parse_buffer,(S)) + +typedef struct { + git_repository *repo; + uint32_t flags; + const char *workdir; + git_index *index; + git_vector *files; +} attr_walk_up_info; + +int git_attr_cache__decide_sources( + uint32_t flags, bool has_wd, bool has_index, git_attr_file_source *srcs) +{ + int count = 0; + + switch (flags & 0x03) { + case GIT_ATTR_CHECK_FILE_THEN_INDEX: + if (has_wd) + srcs[count++] = GIT_ATTR_FILE_FROM_FILE; + if (has_index) + srcs[count++] = GIT_ATTR_FILE_FROM_INDEX; + break; + case GIT_ATTR_CHECK_INDEX_THEN_FILE: + if (has_index) + srcs[count++] = GIT_ATTR_FILE_FROM_INDEX; + if (has_wd) + srcs[count++] = GIT_ATTR_FILE_FROM_FILE; + break; + case GIT_ATTR_CHECK_INDEX_ONLY: + if (has_index) + srcs[count++] = GIT_ATTR_FILE_FROM_INDEX; + break; + } + + return count; +} + +static int push_one_attr(void *ref, git_buf *path) +{ + int error = 0, n_src, i; + attr_walk_up_info *info = (attr_walk_up_info *)ref; + git_attr_file_source src[2]; + + n_src = git_attr_cache__decide_sources( + info->flags, info->workdir != NULL, info->index != NULL, src); + + for (i = 0; !error && i < n_src; ++i) + error = git_attr_cache__push_file( + info->repo, path->ptr, GIT_ATTR_FILE, src[i], + git_attr_file__parse_buffer, info->files); + + return error; +} + +static int collect_attr_files( + git_repository *repo, + uint32_t flags, + const char *path, + git_vector *files) +{ + int error; + git_buf dir = GIT_BUF_INIT; + const char *workdir = git_repository_workdir(repo); + attr_walk_up_info info; + + if (git_attr_cache__init(repo) < 0 || + git_vector_init(files, 4, NULL) < 0) + return -1; + + /* Resolve path in a non-bare repo */ + if (workdir != NULL) + error = git_path_find_dir(&dir, path, workdir); + else + error = git_path_dirname_r(&dir, path); + if (error < 0) + goto cleanup; + + /* in precendence order highest to lowest: + * - $GIT_DIR/info/attributes + * - path components with .gitattributes + * - config core.attributesfile + * - $GIT_PREFIX/etc/gitattributes + */ + + error = push_attr_file( + repo, files, git_repository_path(repo), GIT_ATTR_FILE_INREPO); + if (error < 0) + goto cleanup; + + info.repo = repo; + info.flags = flags; + info.workdir = workdir; + if (git_repository_index__weakptr(&info.index, repo) < 0) + giterr_clear(); /* no error even if there is no index */ + info.files = files; + + error = git_path_walk_up(&dir, workdir, push_one_attr, &info); + if (error < 0) + goto cleanup; + + if (git_repository_attr_cache(repo)->cfg_attr_file != NULL) { + error = push_attr_file( + repo, files, NULL, git_repository_attr_cache(repo)->cfg_attr_file); + if (error < 0) + goto cleanup; + } + + if ((flags & GIT_ATTR_CHECK_NO_SYSTEM) == 0) { + error = git_futils_find_system_file(&dir, GIT_ATTR_FILE_SYSTEM); + if (!error) + error = push_attr_file(repo, files, NULL, dir.ptr); + else if (error == GIT_ENOTFOUND) + error = 0; + } + + cleanup: + if (error < 0) + git_vector_free(files); + git_buf_free(&dir); + + return error; +} + + +int git_attr_cache__init(git_repository *repo) +{ + int ret; + git_attr_cache *cache = git_repository_attr_cache(repo); + git_config *cfg; + + if (cache->initialized) + return 0; + + /* cache config settings for attributes and ignores */ + if (git_repository_config__weakptr(&cfg, repo) < 0) + return -1; + + ret = git_config_get_string(&cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG); + if (ret < 0 && ret != GIT_ENOTFOUND) + return ret; + + ret = git_config_get_string(&cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG); + if (ret < 0 && ret != GIT_ENOTFOUND) + return ret; + + giterr_clear(); + + /* allocate hashtable for attribute and ignore file contents */ + if (cache->files == NULL) { + cache->files = git_strmap_alloc(); + GITERR_CHECK_ALLOC(cache->files); + } + + /* allocate hashtable for attribute macros */ + if (cache->macros == NULL) { + 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 */ + 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; + + cache = git_repository_attr_cache(repo); + + if (cache->files != NULL) { + git_attr_file *file; + + git_strmap_foreach_value(cache->files, file, { + git_attr_file__free(file); + }); + + git_strmap_free(cache->files); + } + + if (cache->macros != NULL) { + git_attr_rule *rule; + + git_strmap_foreach_value(cache->macros, rule, { + git_attr_rule__free(rule); + }); + + git_strmap_free(cache->macros); + } + + git_pool_clear(&cache->pool); + + 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 0; + + 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 new file mode 100644 index 000000000..a35b1160f --- /dev/null +++ b/src/attr.h @@ -0,0 +1,56 @@ +/* + * 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_attr_h__ +#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_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; + +typedef int (*git_attr_file_parser)( + git_repository *, const char *, git_attr_file *); + +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__push_file( + git_repository *repo, + const char *base, + const char *filename, + git_attr_file_source source, + git_attr_file_parser parse, + git_vector *stack); + +extern int git_attr_cache__internal_file( + git_repository *repo, + const char *key, + git_attr_file **file_ptr); + +/* returns true if path is in cache */ +extern bool git_attr_cache__is_cached( + git_repository *repo, git_attr_file_source source, const char *path); + +extern int git_attr_cache__decide_sources( + uint32_t flags, bool has_wd, bool has_index, git_attr_file_source *srcs); + +#endif diff --git a/src/attr_file.c b/src/attr_file.c new file mode 100644 index 000000000..ca2f8fb58 --- /dev/null +++ b/src/attr_file.c @@ -0,0 +1,609 @@ +#include "common.h" +#include "repository.h" +#include "filebuf.h" +#include "git2/blob.h" +#include "git2/tree.h" +#include <ctype.h> + +const char *git_attr__true = "[internal]__TRUE__"; +const char *git_attr__false = "[internal]__FALSE__"; +const char *git_attr__unset = "[internal]__UNSET__"; + +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, + git_attr_file_source from, + const char *path, + git_pool *pool) +{ + git_attr_file *attrs = NULL; + + attrs = git__calloc(1, sizeof(git_attr_file)); + GITERR_CHECK_ALLOC(attrs); + + if (pool) + attrs->pool = pool; + else { + attrs->pool = git__calloc(1, sizeof(git_pool)); + if (!attrs->pool || git_pool_init(attrs->pool, 1, 0) < 0) + goto fail; + attrs->pool_is_allocated = true; + } + + if (path) { + size_t len = strlen(path); + + attrs->key = git_pool_malloc(attrs->pool, (uint32_t)len + 3); + GITERR_CHECK_ALLOC(attrs->key); + + attrs->key[0] = '0' + from; + attrs->key[1] = '#'; + memcpy(&attrs->key[2], path, len); + attrs->key[len + 2] = '\0'; + } + + if (git_vector_init(&attrs->rules, 4, NULL) < 0) + goto fail; + + *attrs_ptr = attrs; + return 0; + +fail: + git_attr_file__free(attrs); + attrs_ptr = NULL; + return -1; +} + +int git_attr_file__parse_buffer( + git_repository *repo, const char *buffer, git_attr_file *attrs) +{ + int error = 0; + const char *scan = NULL; + char *context = NULL; + git_attr_rule *rule = NULL; + + assert(buffer && attrs); + + scan = buffer; + + /* if subdir file path, convert context for file paths */ + if (attrs->key && git__suffixcmp(attrs->key, "/" GIT_ATTR_FILE) == 0) { + context = attrs->key + 2; + context[strlen(context) - strlen(GIT_ATTR_FILE)] = '\0'; + } + + while (!error && *scan) { + /* allocate rule if needed */ + if (!rule && !(rule = git__calloc(1, sizeof(git_attr_rule)))) { + error = -1; + break; + } + + /* parse the next "pattern attr attr attr" line */ + 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 + * file other than .gitattributes at repo root. + */ + error = git_attr_cache__insert_macro(repo, rule); + else + error = git_vector_insert(&attrs->rules, rule); + } + + /* if the rule wasn't a pattern, on to the next */ + if (error < 0) { + git_attr_rule__clear(rule); /* reset rule contents */ + if (error == GIT_ENOTFOUND) + error = 0; + } else { + rule = NULL; /* vector now "owns" the rule */ + } + } + + git_attr_rule__free(rule); + + /* restore file path used for context */ + if (context) + context[strlen(context)] = '.'; /* first char of GIT_ATTR_FILE */ + + return error; +} + +int git_attr_file__new_and_load( + git_attr_file **attrs_ptr, + const char *path) +{ + int error; + git_buf content = GIT_BUF_INIT; + + if ((error = git_attr_file__new(attrs_ptr, 0, path, NULL)) < 0) + return error; + + if (!(error = git_futils_readbuffer(&content, path))) + error = git_attr_file__parse_buffer( + NULL, git_buf_cstr(&content), *attrs_ptr); + + git_buf_free(&content); + + if (error) { + git_attr_file__free(*attrs_ptr); + *attrs_ptr = NULL; + } + + return error; +} + +void git_attr_file__clear_rules(git_attr_file *file) +{ + unsigned int i; + git_attr_rule *rule; + + git_vector_foreach(&file->rules, i, rule) + git_attr_rule__free(rule); + + git_vector_free(&file->rules); +} + +void git_attr_file__free(git_attr_file *file) +{ + if (!file) + return; + + git_attr_file__clear_rules(file); + + if (file->pool_is_allocated) { + git_pool_clear(file->pool); + git__free(file->pool); + } + file->pool = NULL; + + git__free(file); +} + +uint32_t git_attr_file__name_hash(const char *name) +{ + uint32_t h = 5381; + int c; + assert(name); + while ((c = (int)*name++) != 0) + h = ((h << 5) + h) + c; + return h; +} + + +int git_attr_file__lookup_one( + git_attr_file *file, + const git_attr_path *path, + const char *attr, + const char **value) +{ + unsigned int i; + git_attr_name name; + git_attr_rule *rule; + + *value = NULL; + + name.name = attr; + name.name_hash = git_attr_file__name_hash(attr); + + git_attr_file__foreach_matching_rule(file, path, i, rule) { + int pos = git_vector_bsearch(&rule->assigns, &name); + + if (pos >= 0) { + *value = ((git_attr_assignment *) + git_vector_get(&rule->assigns, pos))->value; + break; + } + } + + return 0; +} + + +bool git_attr_fnmatch__match( + git_attr_fnmatch *match, + const git_attr_path *path) +{ + int fnm; + + if (match->flags & GIT_ATTR_FNMATCH_DIRECTORY && !path->is_dir) + return false; + + if (match->flags & GIT_ATTR_FNMATCH_FULLPATH) + fnm = p_fnmatch(match->pattern, path->path, FNM_PATHNAME); + else if (path->is_dir) + fnm = p_fnmatch(match->pattern, path->basename, FNM_LEADING_DIR); + else + fnm = p_fnmatch(match->pattern, path->basename, 0); + + return (fnm == FNM_NOMATCH) ? false : true; +} + +bool git_attr_rule__match( + git_attr_rule *rule, + const git_attr_path *path) +{ + bool matched = git_attr_fnmatch__match(&rule->match, path); + + if (rule->match.flags & GIT_ATTR_FNMATCH_NEGATIVE) + matched = !matched; + + return matched; +} + + +git_attr_assignment *git_attr_rule__lookup_assignment( + git_attr_rule *rule, const char *name) +{ + int pos; + git_attr_name key; + key.name = name; + key.name_hash = git_attr_file__name_hash(name); + + pos = git_vector_bsearch(&rule->assigns, &key); + + return (pos >= 0) ? git_vector_get(&rule->assigns, pos) : NULL; +} + +int git_attr_path__init( + git_attr_path *info, const char *path, const char *base) +{ + /* 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 = info->path; + + info->is_dir = (int)git_path_isdir(info->full.ptr); + + return 0; +} + +void git_attr_path__free(git_attr_path *info) +{ + git_buf_free(&info->full); + info->path = NULL; + info->basename = NULL; +} + + +/* + * From gitattributes(5): + * + * Patterns have the following format: + * + * - A blank line matches no files, so it can serve as a separator for + * readability. + * + * - A line starting with # serves as a comment. + * + * - An optional prefix ! which negates the pattern; any matching file + * excluded by a previous pattern will become included again. If a negated + * pattern matches, this will override lower precedence patterns sources. + * + * - If the pattern ends with a slash, it is removed for the purpose of the + * following description, but it would only find a match with a directory. In + * other words, foo/ will match a directory foo and paths underneath it, but + * will not match a regular file or a symbolic link foo (this is consistent + * with the way how pathspec works in general in git). + * + * - If the pattern does not contain a slash /, git treats it as a shell glob + * pattern and checks for a match against the pathname without leading + * directories. + * + * - Otherwise, git treats the pattern as a shell glob suitable for consumption + * by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will + * not match a / in the pathname. For example, "Documentation/\*.html" matches + * "Documentation/git.html" but not "Documentation/ppc/ppc.html". A leading + * slash matches the beginning of the pathname; for example, "/\*.c" matches + * "cat-file.c" but not "mozilla-sha1/sha1.c". + */ + +/* + * This will return 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) +{ + const char *pattern, *scan; + int slash_count, allow_space; + + assert(spec && base && *base); + + spec->flags = (spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE); + allow_space = (spec->flags != 0); + + pattern = *base; + + while (git__isspace(*pattern)) pattern++; + if (!*pattern || *pattern == '#') { + *base = git__next_line(pattern); + return GIT_ENOTFOUND; + } + + if (*pattern == '[') { + if (strncmp(pattern, "[attr]", 6) == 0) { + spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO; + pattern += 6; + } + /* else a character range like [a-e]* which is accepted */ + } + + if (*pattern == '!') { + spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE; + pattern++; + } + + slash_count = 0; + for (scan = pattern; *scan != '\0'; ++scan) { + /* scan until (non-escaped) white space */ + if (git__isspace(*scan) && *(scan - 1) != '\\') { + if (!allow_space || (*scan != ' ' && *scan != '\t')) + break; + } + + 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 (git__iswildcard(*scan) && + (scan == pattern || (*(scan - 1) != '\\'))) + spec->flags = spec->flags | GIT_ATTR_FNMATCH_HASWILD; + } + + *base = scan; + + spec->length = scan - pattern; + + if (pattern[spec->length - 1] == '/') { + spec->length--; + spec->flags = spec->flags | GIT_ATTR_FNMATCH_DIRECTORY; + if (--slash_count <= 0) + spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH; + } + + if ((spec->flags & GIT_ATTR_FNMATCH_FULLPATH) != 0 && + source != NULL && git_path_root(pattern) < 0) + { + size_t sourcelen = strlen(source); + /* given an unrooted fullpath match from a file inside a repo, + * prefix the pattern with the relative directory of the source file + */ + spec->pattern = git_pool_malloc( + pool, (uint32_t)(sourcelen + spec->length + 1)); + if (spec->pattern) { + memcpy(spec->pattern, source, sourcelen); + memcpy(spec->pattern + sourcelen, pattern, spec->length); + spec->length += sourcelen; + spec->pattern[spec->length] = '\0'; + } + } else { + spec->pattern = git_pool_strndup(pool, pattern, spec->length); + } + + if (!spec->pattern) { + *base = git__next_line(pattern); + return -1; + } else { + /* strip '\' that might have be used for internal whitespace */ + char *to = spec->pattern; + for (scan = spec->pattern; *scan; to++, scan++) { + if (*scan == '\\') + scan++; /* skip '\' but include next char */ + if (to != scan) + *to = *scan; + } + if (to != scan) { + *to = '\0'; + spec->length = (to - spec->pattern); + } + } + + return 0; +} + +static int sort_by_hash_and_name(const void *a_raw, const void *b_raw) +{ + const git_attr_name *a = a_raw; + const git_attr_name *b = b_raw; + + if (b->name_hash < a->name_hash) + return 1; + else if (b->name_hash > a->name_hash) + return -1; + else + return strcmp(b->name, a->name); +} + +static void git_attr_assignment__free(git_attr_assignment *assign) +{ + /* 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; + assign->value = NULL; + git__free(assign); +} + +static int merge_assignments(void **old_raw, void *new_raw) +{ + git_attr_assignment **old = (git_attr_assignment **)old_raw; + git_attr_assignment *new = (git_attr_assignment *)new_raw; + + GIT_REFCOUNT_DEC(*old, git_attr_assignment__free); + *old = new; + return GIT_EEXISTS; +} + +int git_attr_assignment__parse( + git_repository *repo, + git_pool *pool, + git_vector *assigns, + const char **base) +{ + int error; + const char *scan = *base; + git_attr_assignment *assign = NULL; + + assert(assigns && !assigns->length); + + assigns->_cmp = sort_by_hash_and_name; + + while (*scan && *scan != '\n') { + const char *name_start, *value_start; + + /* skip leading blanks */ + while (git__isspace(*scan) && *scan != '\n') scan++; + + /* allocate assign if needed */ + if (!assign) { + assign = git__calloc(1, sizeof(git_attr_assignment)); + GITERR_CHECK_ALLOC(assign); + GIT_REFCOUNT_INC(assign); + } + + assign->name_hash = 5381; + assign->value = git_attr__true; + + /* look for magic name prefixes */ + if (*scan == '-') { + assign->value = git_attr__false; + scan++; + } else if (*scan == '!') { + assign->value = git_attr__unset; /* explicit unspecified state */ + scan++; + } else if (*scan == '#') /* comment rest of line */ + break; + + /* find the name */ + name_start = scan; + while (*scan && !git__isspace(*scan) && *scan != '=') { + assign->name_hash = + ((assign->name_hash << 5) + assign->name_hash) + *scan; + scan++; + } + if (scan == name_start) { + /* must have found lone prefix (" - ") or leading = ("=foo") + * or end of buffer -- advance until whitespace and continue + */ + while (*scan && !git__isspace(*scan)) scan++; + continue; + } + + /* allocate permanent storage for name */ + 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 == '=') { + for (value_start = ++scan; *scan && !git__isspace(*scan); ++scan); + + /* if we found a value, allocate permanent storage for it */ + if (scan > value_start) { + 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_attr_cache__lookup_macro(repo, assign->name); + + if (macro != NULL) { + unsigned int i; + git_attr_assignment *massign; + + git_vector_foreach(¯o->assigns, i, massign) { + GIT_REFCOUNT_INC(massign); + + error = git_vector_insert_sorted( + assigns, massign, &merge_assignments); + if (error < 0 && error != GIT_EEXISTS) + return error; + } + } + } + + /* insert allocated assign into vector */ + error = git_vector_insert_sorted(assigns, assign, &merge_assignments); + if (error < 0 && error != GIT_EEXISTS) + return error; + + /* clear assign since it is now "owned" by the vector */ + assign = NULL; + } + + if (assign != NULL) + git_attr_assignment__free(assign); + + *base = git__next_line(scan); + + return (assigns->length == 0) ? GIT_ENOTFOUND : 0; +} + +static void git_attr_rule__clear(git_attr_rule *rule) +{ + unsigned int i; + git_attr_assignment *assign; + + if (!rule) + return; + + if (!(rule->match.flags & GIT_ATTR_FNMATCH_IGNORE)) { + git_vector_foreach(&rule->assigns, i, assign) + GIT_REFCOUNT_DEC(assign, git_attr_assignment__free); + git_vector_free(&rule->assigns); + } + + /* match.pattern is stored in a git_pool, so no need to free */ + rule->match.pattern = NULL; + rule->match.length = 0; +} + +void git_attr_rule__free(git_attr_rule *rule) +{ + git_attr_rule__clear(rule); + git__free(rule); +} + diff --git a/src/attr_file.h b/src/attr_file.h new file mode 100644 index 000000000..7939f838a --- /dev/null +++ b/src/attr_file.h @@ -0,0 +1,145 @@ +/* + * 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_attr_file_h__ +#define INCLUDE_attr_file_h__ + +#include "git2/attr.h" +#include "vector.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_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) +#define GIT_ATTR_FNMATCH_ALLOWSPACE (1U << 6) + +typedef struct { + char *pattern; + size_t length; + unsigned int flags; +} git_attr_fnmatch; + +typedef struct { + git_attr_fnmatch match; + git_vector assigns; /* vector of <git_attr_assignment*> */ +} git_attr_rule; + +typedef struct { + git_refcount unused; + const char *name; + uint32_t name_hash; +} git_attr_name; + +typedef struct { + git_refcount rc; /* for macros */ + char *name; + uint32_t name_hash; + const char *value; +} git_attr_assignment; + +typedef struct { + git_time_t seconds; + git_off_t size; + unsigned int ino; +} git_attr_file_stat_sig; + +typedef struct { + char *key; /* cache "source#path" this was loaded from */ + git_vector rules; /* vector of <rule*> or <fnmatch*> */ + git_pool *pool; + bool pool_is_allocated; + union { + git_oid oid; + git_attr_file_stat_sig st; + } cache_data; +} git_attr_file; + +typedef struct { + git_buf full; + const char *path; + const char *basename; + int is_dir; +} git_attr_path; + +typedef enum { + GIT_ATTR_FILE_FROM_FILE = 0, + GIT_ATTR_FILE_FROM_INDEX = 1 +} git_attr_file_source; + +/* + * git_attr_file API + */ + +extern int git_attr_file__new( + git_attr_file **attrs_ptr, git_attr_file_source src, const char *path, git_pool *pool); + +extern int git_attr_file__new_and_load( + git_attr_file **attrs_ptr, const char *path); + +extern void git_attr_file__free(git_attr_file *file); + +extern void git_attr_file__clear_rules(git_attr_file *file); + +extern int git_attr_file__parse_buffer( + git_repository *repo, const char *buf, git_attr_file *file); + +extern int git_attr_file__lookup_one( + git_attr_file *file, + const git_attr_path *path, + const char *attr, + const char **value); + +/* loop over rules in file from bottom to top */ +#define git_attr_file__foreach_matching_rule(file, path, iter, rule) \ + git_vector_rforeach(&(file)->rules, (iter), (rule)) \ + if (git_attr_rule__match((rule), (path))) + +extern uint32_t git_attr_file__name_hash(const char *name); + + +/* + * other utilities + */ + +extern int git_attr_fnmatch__parse( + git_attr_fnmatch *spec, + git_pool *pool, + const char *source, + const char **base); + +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 bool git_attr_rule__match( + git_attr_rule *rule, + const git_attr_path *path); + +extern git_attr_assignment *git_attr_rule__lookup_assignment( + git_attr_rule *rule, const char *name); + +extern int git_attr_path__init( + git_attr_path *info, const char *path, 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); + +#endif diff --git a/src/backends/hiredis.c b/src/backends/hiredis.c deleted file mode 100644 index 111abf7d3..000000000 --- a/src/backends/hiredis.c +++ /dev/null @@ -1,217 +0,0 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#include "common.h" -#include "git2/object.h" -#include "hash.h" -#include "odb.h" - -#include "git2/odb_backend.h" - -#ifdef GIT2_HIREDIS_BACKEND - -#include <hiredis/hiredis.h> - -typedef struct { - git_odb_backend parent; - - redisContext *db; -} hiredis_backend; - -int hiredis_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_backend *_backend, const git_oid *oid) { - hiredis_backend *backend; - int error; - redisReply *reply; - - assert(len_p && type_p && _backend && oid); - - backend = (hiredis_backend *) _backend; - error = GIT_ERROR; - - reply = redisCommand(backend->db, "HMGET %b %s %s", oid->id, GIT_OID_RAWSZ, - "type", "size"); - - if (reply && reply->type == REDIS_REPLY_ARRAY) { - if (reply->element[0]->type != REDIS_REPLY_NIL && - reply->element[0]->type != REDIS_REPLY_NIL) { - *type_p = (git_otype) atoi(reply->element[0]->str); - *len_p = (size_t) atoi(reply->element[1]->str); - error = GIT_SUCCESS; - } else { - error = GIT_ENOTFOUND; - } - } else { - error = GIT_ERROR; - } - - freeReplyObject(reply); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to read header"); -} - -int hiredis_backend__read(void **data_p, size_t *len_p, git_otype *type_p, git_odb_backend *_backend, const git_oid *oid) { - hiredis_backend *backend; - int error; - redisReply *reply; - - assert(data_p && len_p && type_p && _backend && oid); - - backend = (hiredis_backend *) _backend; - error = GIT_ERROR; - - reply = redisCommand(backend->db, "HMGET %b %s %s %s", oid->id, GIT_OID_RAWSZ, - "type", "size", "data"); - - if (reply && reply->type == REDIS_REPLY_ARRAY) { - if (reply->element[0]->type != REDIS_REPLY_NIL && - reply->element[1]->type != REDIS_REPLY_NIL && - reply->element[2]->type != REDIS_REPLY_NIL) { - *type_p = (git_otype) atoi(reply->element[0]->str); - *len_p = (size_t) atoi(reply->element[1]->str); - *data_p = git__malloc(*len_p); - if (*data_p == NULL) { - error = GIT_ENOMEM; - } else { - memcpy(*data_p, reply->element[2]->str, *len_p); - error = GIT_SUCCESS; - } - } else { - error = GIT_ENOTFOUND; - } - } else { - error = GIT_ERROR; - } - - freeReplyObject(reply); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to read backend"); -} - -int hiredis_backend__read_prefix(git_oid *out_oid, void **data_p, size_t *len_p, git_otype *type_p, git_odb_backend *_backend, - const git_oid *short_oid, unsigned int len) { - if (len >= GIT_OID_HEXSZ) { - /* Just match the full identifier */ - int error = hiredis_backend__read(data_p, len_p, type_p, backend, short_oid); - if (error == GIT_SUCCESS) - git_oid_cpy(out_oid, short_oid); - - return error; - } else if (len < GIT_OID_HEXSZ) { - /* TODO */ - return git__throw(GIT_ENOTIMPLEMENTED, "Hiredis backend cannot search objects from short oid"); - } -} - -int hiredis_backend__exists(git_odb_backend *_backend, const git_oid *oid) { - hiredis_backend *backend; - int found; - redisReply *reply; - - assert(_backend && oid); - - backend = (hiredis_backend *) _backend; - found = 0; - - reply = redisCommand(backend->db, "exists %b", oid->id, GIT_OID_RAWSZ); - if (reply && reply->type != REDIS_REPLY_NIL && reply->type != REDIS_REPLY_ERROR) - found = 1; - - - freeReplyObject(reply); - return found; -} - -int hiredis_backend__write(git_oid *id, git_odb_backend *_backend, const void *data, size_t len, git_otype type) { - hiredis_backend *backend; - int error; - redisReply *reply; - - assert(id && _backend && data); - - backend = (hiredis_backend *) _backend; - error = GIT_ERROR; - - if ((error = git_odb_hash(id, data, len, type)) < 0) - return git__rethrow(error, "Failed to write backend"); - - reply = redisCommand(backend->db, "HMSET %b " - "type %d " - "size %d " - "data %b ", id->id, GIT_OID_RAWSZ, - (int) type, len, data, len); - - error = (reply == NULL || reply->type == REDIS_REPLY_ERROR) ? GIT_ERROR : GIT_SUCCESS; - - freeReplyObject(reply); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write backend"); -} - -void hiredis_backend__free(git_odb_backend *_backend) { - hiredis_backend *backend; - assert(_backend); - backend = (hiredis_backend *) _backend; - - redisFree(backend->db); - - free(backend); -} - -int git_odb_backend_hiredis(git_odb_backend **backend_out, const char *host, int port) { - hiredis_backend *backend; - - backend = git__calloc(1, sizeof (hiredis_backend)); - if (backend == NULL) - return GIT_ENOMEM; - - - backend->db = redisConnect(host, port); - if (backend->db->err) - goto cleanup; - - backend->parent.read = &hiredis_backend__read; - backend->parent.read_prefix = &hiredis_backend__read_prefix; - backend->parent.read_header = &hiredis_backend__read_header; - backend->parent.write = &hiredis_backend__write; - backend->parent.exists = &hiredis_backend__exists; - backend->parent.free = &hiredis_backend__free; - - *backend_out = (git_odb_backend *) backend; - - return GIT_SUCCESS; -cleanup: - free(backend); - return git__throw(GIT_ERROR, "Failed to get ODB backend"); -} - -#else - -int git_odb_backend_hiredis(git_odb_backend ** GIT_UNUSED(backend_out), - const char *GIT_UNUSED(host), int GIT_UNUSED(port)) { - GIT_UNUSED_ARG(backend_out); - GIT_UNUSED_ARG(host); - GIT_UNUSED_ARG(port); - return git__throw(GIT_ENOTIMPLEMENTED, "Failed to get ODB backend. Feature not yet implemented"); -} - - -#endif /* HAVE_HIREDIS */ diff --git a/src/backends/sqlite.c b/src/backends/sqlite.c deleted file mode 100644 index 8626349ec..000000000 --- a/src/backends/sqlite.c +++ /dev/null @@ -1,296 +0,0 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#include "common.h" -#include "git2/object.h" -#include "hash.h" -#include "odb.h" - -#include "git2/odb_backend.h" - -#ifdef GIT2_SQLITE_BACKEND - -#include <sqlite3.h> - -#define GIT2_TABLE_NAME "git2_odb" - -typedef struct { - git_odb_backend parent; - sqlite3 *db; - sqlite3_stmt *st_read; - sqlite3_stmt *st_write; - sqlite3_stmt *st_read_header; -} sqlite_backend; - -int sqlite_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_backend *_backend, const git_oid *oid) -{ - sqlite_backend *backend; - int error; - - assert(len_p && type_p && _backend && oid); - - backend = (sqlite_backend *)_backend; - error = GIT_ERROR; - - if (sqlite3_bind_text(backend->st_read_header, 1, (char *)oid->id, 20, SQLITE_TRANSIENT) == SQLITE_OK) { - if (sqlite3_step(backend->st_read_header) == SQLITE_ROW) { - *type_p = (git_otype)sqlite3_column_int(backend->st_read_header, 0); - *len_p = (size_t)sqlite3_column_int(backend->st_read_header, 1); - assert(sqlite3_step(backend->st_read_header) == SQLITE_DONE); - error = GIT_SUCCESS; - } else { - error = GIT_ENOTFOUND; - } - } - - sqlite3_reset(backend->st_read_header); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "SQLite backend: Failed to read header"); -} - - -int sqlite_backend__read(void **data_p, size_t *len_p, git_otype *type_p, git_odb_backend *_backend, const git_oid *oid) -{ - sqlite_backend *backend; - int error; - - assert(data_p && len_p && type_p && _backend && oid); - - backend = (sqlite_backend *)_backend; - error = GIT_ERROR; - - if (sqlite3_bind_text(backend->st_read, 1, (char *)oid->id, 20, SQLITE_TRANSIENT) == SQLITE_OK) { - if (sqlite3_step(backend->st_read) == SQLITE_ROW) { - *type_p = (git_otype)sqlite3_column_int(backend->st_read, 0); - *len_p = (size_t)sqlite3_column_int(backend->st_read, 1); - *data_p = git__malloc(*len_p); - - if (*data_p == NULL) { - error = GIT_ENOMEM; - } else { - memcpy(*data_p, sqlite3_column_blob(backend->st_read, 2), *len_p); - error = GIT_SUCCESS; - } - - assert(sqlite3_step(backend->st_read) == SQLITE_DONE); - } else { - error = GIT_ENOTFOUND; - } - } - - sqlite3_reset(backend->st_read); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "SQLite backend: Failed to read"); -} - -int sqlite_backend__read_prefix(git_oid *out_oid, void **data_p, size_t *len_p, git_otype *type_p, git_odb_backend *_backend, - const git_oid *short_oid, unsigned int len) { - if (len >= GIT_OID_HEXSZ) { - /* Just match the full identifier */ - int error = sqlite_backend__read(data_p, len_p, type_p, _backend, short_oid); - if (error == GIT_SUCCESS) - git_oid_cpy(out_oid, short_oid); - - return error; - } else if (len < GIT_OID_HEXSZ) { - /* TODO */ - return git__throw(GIT_ENOTIMPLEMENTED, "Sqlite backend cannot search objects from short oid"); - } -} - -int sqlite_backend__exists(git_odb_backend *_backend, const git_oid *oid) -{ - sqlite_backend *backend; - int found; - - assert(_backend && oid); - - backend = (sqlite_backend *)_backend; - found = 0; - - if (sqlite3_bind_text(backend->st_read_header, 1, (char *)oid->id, 20, SQLITE_TRANSIENT) == SQLITE_OK) { - if (sqlite3_step(backend->st_read_header) == SQLITE_ROW) { - found = 1; - assert(sqlite3_step(backend->st_read_header) == SQLITE_DONE); - } - } - - sqlite3_reset(backend->st_read_header); - return found; -} - - -int sqlite_backend__write(git_oid *id, git_odb_backend *_backend, const void *data, size_t len, git_otype type) -{ - int error; - sqlite_backend *backend; - - assert(id && _backend && data); - - backend = (sqlite_backend *)_backend; - - if ((error = git_odb_hash(id, data, len, type)) < 0) - return git__rethrow(error, "SQLite backend: Failed to write"); - - error = SQLITE_ERROR; - - if (sqlite3_bind_text(backend->st_write, 1, (char *)id->id, 20, SQLITE_TRANSIENT) == SQLITE_OK && - sqlite3_bind_int(backend->st_write, 2, (int)type) == SQLITE_OK && - sqlite3_bind_int(backend->st_write, 3, len) == SQLITE_OK && - sqlite3_bind_blob(backend->st_write, 4, data, len, SQLITE_TRANSIENT) == SQLITE_OK) { - error = sqlite3_step(backend->st_write); - } - - sqlite3_reset(backend->st_write); - return (error == SQLITE_DONE) ? GIT_SUCCESS : git__throw(GIT_ERROR, "SQLite backend: Failed to write"); -} - - -void sqlite_backend__free(git_odb_backend *_backend) -{ - sqlite_backend *backend; - assert(_backend); - backend = (sqlite_backend *)_backend; - - sqlite3_finalize(backend->st_read); - sqlite3_finalize(backend->st_read_header); - sqlite3_finalize(backend->st_write); - sqlite3_close(backend->db); - - free(backend); -} - -static int create_table(sqlite3 *db) -{ - static const char *sql_creat = - "CREATE TABLE '" GIT2_TABLE_NAME "' (" - "'oid' CHARACTER(20) PRIMARY KEY NOT NULL," - "'type' INTEGER NOT NULL," - "'size' INTEGER NOT NULL," - "'data' BLOB);"; - - if (sqlite3_exec(db, sql_creat, NULL, NULL, NULL) != SQLITE_OK) - return git__throw(GIT_ERROR, "SQLite backend: Failed to create table"); - - return GIT_SUCCESS; -} - -static int init_db(sqlite3 *db) -{ - static const char *sql_check = - "SELECT name FROM sqlite_master WHERE type='table' AND name='" GIT2_TABLE_NAME "';"; - - sqlite3_stmt *st_check; - int error; - - if (sqlite3_prepare_v2(db, sql_check, -1, &st_check, NULL) != SQLITE_OK) - return git__throw(GIT_ERROR, "SQLite backend: Failed to initialize database"); - - switch (sqlite3_step(st_check)) { - case SQLITE_DONE: - /* the table was not found */ - error = create_table(db); - break; - - case SQLITE_ROW: - /* the table was found */ - error = GIT_SUCCESS; - break; - - default: - error = GIT_ERROR; - break; - } - - sqlite3_finalize(st_check); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "SQLite backend: Failed to initialize database"); -} - -static int init_statements(sqlite_backend *backend) -{ - static const char *sql_read = - "SELECT type, size, data FROM '" GIT2_TABLE_NAME "' WHERE oid = ?;"; - - static const char *sql_read_header = - "SELECT type, size FROM '" GIT2_TABLE_NAME "' WHERE oid = ?;"; - - static const char *sql_write = - "INSERT OR IGNORE INTO '" GIT2_TABLE_NAME "' VALUES (?, ?, ?, ?);"; - - if (sqlite3_prepare_v2(backend->db, sql_read, -1, &backend->st_read, NULL) != SQLITE_OK) - return git__throw(GIT_ERROR, "SQLite backend: Failed to initialize statements"); - - if (sqlite3_prepare_v2(backend->db, sql_read_header, -1, &backend->st_read_header, NULL) != SQLITE_OK) - return git__throw(GIT_ERROR, "SQLite backend: Failed to initialize statements"); - - if (sqlite3_prepare_v2(backend->db, sql_write, -1, &backend->st_write, NULL) != SQLITE_OK) - return git__throw(GIT_ERROR, "SQLite backend: Failed to initialize statements"); - - return GIT_SUCCESS; -} - -int git_odb_backend_sqlite(git_odb_backend **backend_out, const char *sqlite_db) -{ - sqlite_backend *backend; - int error; - - backend = git__calloc(1, sizeof(sqlite_backend)); - if (backend == NULL) - return GIT_ENOMEM; - - if (sqlite3_open(sqlite_db, &backend->db) != SQLITE_OK) - goto cleanup; - - error = init_db(backend->db); - if (error < 0) - goto cleanup; - - error = init_statements(backend); - if (error < 0) - goto cleanup; - - backend->parent.read = &sqlite_backend__read; - backend->parent.read_prefix = &sqlite_backend__read_prefix; - backend->parent.read_header = &sqlite_backend__read_header; - backend->parent.write = &sqlite_backend__write; - backend->parent.exists = &sqlite_backend__exists; - backend->parent.free = &sqlite_backend__free; - - *backend_out = (git_odb_backend *)backend; - return GIT_SUCCESS; - -cleanup: - sqlite_backend__free((git_odb_backend *)backend); - return git__throw(GIT_ERROR, "SQLite backend: Failed to get ODB backend"); -} - -#else - -int git_odb_backend_sqlite(git_odb_backend **GIT_UNUSED(backend_out), const char *GIT_UNUSED(sqlite_db)) -{ - GIT_UNUSED_ARG(backend_out); - GIT_UNUSED_ARG(sqlite_db); - return git__throw(GIT_ENOTIMPLEMENTED, "SQLite backend: Failed to get ODB backend. Operation not yet implemented"); -} - -#endif /* HAVE_SQLITE3 */ diff --git a/src/blob.c b/src/blob.c index 6ab58d6b2..699adec6b 100644 --- a/src/blob.c +++ b/src/blob.c @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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 "git2/common.h" @@ -29,6 +11,7 @@ #include "common.h" #include "blob.h" +#include "filter.h" const void *git_blob_rawcontent(git_blob *blob) { @@ -36,16 +19,22 @@ const void *git_blob_rawcontent(git_blob *blob) return blob->odb_object->raw.data; } -int git_blob_rawsize(git_blob *blob) +size_t git_blob_rawsize(git_blob *blob) { assert(blob); return blob->odb_object->raw.len; } +int git_blob__getbuf(git_buf *buffer, git_blob *blob) +{ + return git_buf_set( + buffer, blob->odb_object->raw.data, blob->odb_object->raw.len); +} + void git_blob__free(git_blob *blob) { - git_odb_object_close(blob->odb_object); - free(blob); + git_odb_object_free(blob->odb_object); + git__free(blob); } int git_blob__parse(git_blob *blob, git_odb_object *odb_obj) @@ -53,79 +42,247 @@ 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) { int error; + git_odb *odb; git_odb_stream *stream; - if ((error = git_odb_open_wstream(&stream, repo->db, len, GIT_OBJ_BLOB)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to create blob"); + 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 = stream->write(stream, buffer, len)) < GIT_SUCCESS) { - stream->free(stream); + if ((error = stream->write(stream, buffer, len)) == 0) + error = stream->finalize_write(oid, stream); + + stream->free(stream); + return error; +} + +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, (size_t)file_size, GIT_OBJ_BLOB)) < 0) return error; + + if ((fd = git_futils_open_ro(path)) < 0) { + stream->free(stream); + return -1; + } + + while (!error && file_size > 0) { + ssize_t read_len = p_read(fd, buffer, sizeof(buffer)); + + if (read_len < 0) { + giterr_set( + GITERR_OS, "Failed to create blob. Can't read whole file"); + error = -1; + } + else if (!(error = stream->write(stream, buffer, read_len))) + file_size -= read_len; } - error = stream->finalize_write(oid, stream); + p_close(fd); + + if (!error) + error = stream->finalize_write(oid, stream); + stream->free(stream); + return error; +} + +static int write_file_filtered( + git_oid *oid, + git_odb *odb, + const char *full_path, + git_vector *filters) +{ + int error; + git_buf source = GIT_BUF_INIT; + git_buf dest = GIT_BUF_INIT; + + if ((error = git_futils_readbuffer(&source, full_path)) < 0) + return error; + + error = git_filters_apply(&dest, &source, filters); + + /* Free the source as soon as possible. This can be big in memory, + * and we don't want to ODB write to choke */ + git_buf_free(&source); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to create blob"); + /* 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); - return GIT_SUCCESS; + git_buf_free(&dest); + return error; } -int git_blob_create_fromfile(git_oid *oid, git_repository *repo, const char *path) +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); + GITERR_CHECK_ALLOC(link_data); + + read_len = p_readlink(path, link_data, link_size); + if (read_len != (ssize_t)link_size) { + 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); + git__free(link_data); + return error; +} + +static int blob_create_internal(git_oid *oid, git_repository *repo, const char *content_path, const char *hint_path, bool try_load_filters) { - int error, fd; - char full_path[GIT_PATH_MAX]; - char buffer[2048]; + int error; + struct stat st; + git_odb *odb = NULL; git_off_t size; - git_odb_stream *stream; - if (repo->path_workdir == NULL) - return git__throw(GIT_ENOTFOUND, "Failed to create blob. (No working directory found)"); + assert(hint_path || !try_load_filters); - git__joinpath(full_path, repo->path_workdir, path); + if ((error = git_path_lstat(content_path, &st)) < 0 || (error = git_repository_odb__weakptr(&odb, repo)) < 0) + return error; - if ((fd = gitfo_open(full_path, O_RDONLY)) < 0) - return git__throw(GIT_ENOTFOUND, "Failed to create blob. Could not open '%s'", full_path); + size = st.st_size; - if ((size = gitfo_size(fd)) < 0 || !git__is_sizet(size)) { - gitfo_close(fd); - return git__throw(GIT_EOSERR, "Failed to create blob. '%s' appears to be corrupted", full_path); - } + if (S_ISLNK(st.st_mode)) { + error = write_symlink(oid, odb, content_path, (size_t)size); + } else { + git_vector write_filters = GIT_VECTOR_INIT; + int filter_count = 0; + + if (try_load_filters) { + /* Load the filters for writing this file to the ODB */ + filter_count = git_filters_load( + &write_filters, repo, hint_path, GIT_FILTER_TO_ODB); + } + + if (filter_count < 0) { + /* Negative value means there was a critical error */ + error = filter_count; + } else if (filter_count == 0) { + /* No filters need to be applied to the document: we can stream + * directly from disk */ + error = write_file_stream(oid, odb, content_path, size); + } else { + /* We need to apply one or more filters */ + error = write_file_filtered(oid, odb, content_path, &write_filters); + } - if ((error = git_odb_open_wstream(&stream, repo->db, (size_t)size, GIT_OBJ_BLOB)) < GIT_SUCCESS) { - gitfo_close(fd); - return git__rethrow(error, "Failed to create blob"); + 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. + * + * 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 + */ } - while (size > 0) { - ssize_t read_len; + return error; +} - read_len = read(fd, buffer, sizeof(buffer)); +int git_blob_create_fromfile(git_oid *oid, git_repository *repo, const char *path) +{ + git_buf full_path = GIT_BUF_INIT; + const char *workdir; + int error; - if (read_len < 0) { - gitfo_close(fd); - stream->free(stream); - return git__throw(GIT_EOSERR, "Failed to create blob. Can't read full file"); - } + workdir = git_repository_workdir(repo); + assert(workdir); /* error to call this on bare repo */ - stream->write(stream, buffer, read_len); - size -= read_len; + if (git_buf_joinpath(&full_path, workdir, path) < 0) { + git_buf_free(&full_path); + return -1; } - error = stream->finalize_write(oid, stream); - stream->free(stream); - gitfo_close(fd); + error = blob_create_internal(oid, repo, git_buf_cstr(&full_path), git_buf_cstr(&full_path), true); + + git_buf_free(&full_path); + return error; +} + +int git_blob_create_fromdisk(git_oid *oid, git_repository *repo, const char *path) +{ + int error; + git_buf full_path = GIT_BUF_INIT; + + if ((error = git_path_prettify(&full_path, path, NULL)) < 0) { + git_buf_free(&full_path); + return error; + } - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to create blob"); + error = blob_create_internal(oid, repo, git_buf_cstr(&full_path), git_buf_cstr(&full_path), true); - return GIT_SUCCESS; + git_buf_free(&full_path); + return error; } +#define BUFFER_SIZE 4096 + +int git_blob_create_fromchunks( + git_oid *oid, + git_repository *repo, + const char *hintpath, + int (*source_cb)(char *content, size_t max_length, void *payload), + void *payload) +{ + int error = -1, read_bytes; + char *content = NULL; + git_filebuf file = GIT_FILEBUF_INIT; + + content = git__malloc(BUFFER_SIZE); + GITERR_CHECK_ALLOC(content); + + if (git_filebuf_open(&file, hintpath == NULL ? "streamed" : hintpath, GIT_FILEBUF_TEMPORARY) < 0) + goto cleanup; + + while (1) { + read_bytes = source_cb(content, BUFFER_SIZE, payload); + + assert(read_bytes <= BUFFER_SIZE); + + if (read_bytes <= 0) + break; + + if (git_filebuf_write(&file, content, read_bytes) < 0) + goto cleanup; + } + + if (read_bytes < 0) + goto cleanup; + + if (git_filebuf_flush(&file) < 0) + goto cleanup; + + error = blob_create_internal(oid, repo, file.path_lock, hintpath, hintpath != NULL); + +cleanup: + git_filebuf_cleanup(&file); + git__free(content); + return error; +} diff --git a/src/blob.h b/src/blob.h index 4300d7e54..0305e9473 100644 --- a/src/blob.h +++ b/src/blob.h @@ -1,3 +1,9 @@ +/* + * 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_blob_h__ #define INCLUDE_blob_h__ @@ -13,5 +19,6 @@ struct git_blob { void git_blob__free(git_blob *blob); int git_blob__parse(git_blob *blob, git_odb_object *obj); +int git_blob__getbuf(git_buf *buffer, git_blob *blob); #endif diff --git a/src/branch.c b/src/branch.c new file mode 100644 index 000000000..5d5a24038 --- /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_t 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) + return 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_LISTALL, &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/bswap.h b/src/bswap.h index b9211c3c8..995767a14 100644 --- a/src/bswap.h +++ b/src/bswap.h @@ -1,8 +1,8 @@ /* - * Let's make sure we always have a sane definition for ntohl()/htonl(). - * Some libraries define those as a function call, just to perform byte - * shifting, bringing significant overhead to what should be a simple - * operation. + * 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" @@ -14,8 +14,8 @@ GIT_INLINE(uint32_t) default_swab32(uint32_t val) { return (((val & 0xff000000) >> 24) | - ((val & 0x00ff0000) >> 8) | - ((val & 0x0000ff00) << 8) | + ((val & 0x00ff0000) >> 8) | + ((val & 0x0000ff00) << 8) | ((val & 0x000000ff) << 24)); } diff --git a/src/buffer.c b/src/buffer.c new file mode 100644 index 000000000..783a36eb8 --- /dev/null +++ b/src/buffer.c @@ -0,0 +1,461 @@ +/* + * 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 "buffer.h" +#include "posix.h" +#include <stdarg.h> +#include <ctype.h> + +/* Used as default value for git_buf->ptr so that people can always + * assume ptr is non-NULL and zero terminated even for new git_bufs. + */ +char git_buf__initbuf[1]; + +char git_buf__oom[1]; + +#define ENSURE_SIZE(b, d) \ + if ((d) > buf->asize && git_buf_grow(b, (d)) < 0)\ + return -1; + + +void git_buf_init(git_buf *buf, size_t initial_size) +{ + buf->asize = 0; + buf->size = 0; + buf->ptr = git_buf__initbuf; + + if (initial_size) + git_buf_grow(buf, initial_size); +} + +int git_buf_grow(git_buf *buf, size_t target_size) +{ + int error = git_buf_try_grow(buf, target_size); + if (error != 0) + buf->ptr = git_buf__oom; + return error; +} + +int git_buf_try_grow(git_buf *buf, size_t target_size) +{ + char *new_ptr; + size_t new_size; + + if (buf->ptr == git_buf__oom) + return -1; + + if (target_size <= buf->asize) + return 0; + + if (buf->asize == 0) { + new_size = target_size; + new_ptr = NULL; + } else { + new_size = buf->asize; + new_ptr = buf->ptr; + } + + /* grow the buffer size by 1.5, until it's big enough + * to fit our target size */ + while (new_size < target_size) + new_size = (new_size << 1) - (new_size >> 1); + + /* round allocation up to multiple of 8 */ + new_size = (new_size + 7) & ~7; + + new_ptr = git__realloc(new_ptr, new_size); + if (!new_ptr) + return -1; + + buf->asize = new_size; + buf->ptr = new_ptr; + + /* truncate the existing buffer size if necessary */ + if (buf->size >= buf->asize) + buf->size = buf->asize - 1; + buf->ptr[buf->size] = '\0'; + + return 0; +} + +void git_buf_free(git_buf *buf) +{ + if (!buf) return; + + if (buf->ptr != git_buf__initbuf && buf->ptr != git_buf__oom) + git__free(buf->ptr); + + git_buf_init(buf, 0); +} + +void git_buf_clear(git_buf *buf) +{ + buf->size = 0; + if (buf->asize > 0) + buf->ptr[0] = '\0'; +} + +int git_buf_set(git_buf *buf, const char *data, size_t len) +{ + if (len == 0 || data == NULL) { + git_buf_clear(buf); + } else { + if (data != buf->ptr) { + ENSURE_SIZE(buf, len + 1); + memmove(buf->ptr, data, len); + } + buf->size = len; + buf->ptr[buf->size] = '\0'; + } + return 0; +} + +int git_buf_sets(git_buf *buf, const char *string) +{ + return git_buf_set(buf, string, string ? strlen(string) : 0); +} + +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 0; +} + +int git_buf_put(git_buf *buf, const char *data, size_t len) +{ + ENSURE_SIZE(buf, buf->size + len + 1); + memmove(buf->ptr + buf->size, data, len); + buf->size += len; + buf->ptr[buf->size] = '\0'; + return 0; +} + +int git_buf_puts(git_buf *buf, const char *string) +{ + assert(string); + return git_buf_put(buf, string, strlen(string)); +} + +int git_buf_vprintf(git_buf *buf, const char *format, va_list ap) +{ + int len; + + ENSURE_SIZE(buf, buf->size + (strlen(format) * 2)); + + while (1) { + va_list args; + va_copy(args, ap); + + len = p_vsnprintf( + buf->ptr + buf->size, + buf->asize - buf->size, + format, args + ); + + if (len < 0) { + git__free(buf->ptr); + buf->ptr = git_buf__oom; + return -1; + } + + if ((size_t)len + 1 <= buf->asize - buf->size) { + buf->size += len; + break; + } + + ENSURE_SIZE(buf, buf->size + len + 1); + } + + return 0; +} + +int git_buf_printf(git_buf *buf, const char *format, ...) +{ + int r; + va_list ap; + + va_start(ap, format); + r = git_buf_vprintf(buf, format, ap); + va_end(ap); + + return r; +} + +void git_buf_copy_cstr(char *data, size_t datasize, const git_buf *buf) +{ + size_t copylen; + + assert(data && datasize && buf); + + data[0] = '\0'; + + if (buf->size == 0 || buf->asize <= 0) + return; + + copylen = buf->size; + if (copylen > datasize - 1) + copylen = datasize - 1; + memmove(data, buf->ptr, copylen); + data[copylen] = '\0'; +} + +void git_buf_consume(git_buf *buf, const char *end) +{ + if (end > buf->ptr && end <= buf->ptr + buf->size) { + size_t consumed = end - buf->ptr; + memmove(buf->ptr, end, buf->size - consumed); + buf->size -= consumed; + buf->ptr[buf->size] = '\0'; + } +} + +void git_buf_truncate(git_buf *buf, size_t len) +{ + if (len < buf->size) { + buf->size = len; + buf->ptr[buf->size] = '\0'; + } +} + +void git_buf_rtruncate_at_char(git_buf *buf, char separator) +{ + 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) +{ + git_buf t = *buf_a; + *buf_a = *buf_b; + *buf_b = t; +} + +char *git_buf_detach(git_buf *buf) +{ + char *data = buf->ptr; + + if (buf->asize == 0 || buf->ptr == git_buf__oom) + return NULL; + + git_buf_init(buf, 0); + + return data; +} + +void git_buf_attach(git_buf *buf, char *ptr, size_t asize) +{ + git_buf_free(buf); + + if (ptr) { + buf->ptr = ptr; + buf->size = strlen(ptr); + if (asize) + buf->asize = (asize < buf->size) ? buf->size + 1 : asize; + else /* pass 0 to fall back on strlen + 1 */ + buf->asize = buf->size + 1; + } else { + git_buf_grow(buf, asize); + } +} + +int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...) +{ + va_list ap; + 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 */ + + /* Make two passes to avoid multiple reallocation */ + + va_start(ap, nbuf); + for (i = 0; i < nbuf; ++i) { + const char* segment; + size_t segment_len; + + segment = va_arg(ap, const char *); + if (!segment) + continue; + + segment_len = strlen(segment); + total_size += segment_len; + if (segment_len == 0 || segment[segment_len - 1] != separator) + ++total_size; /* space for separator */ + } + va_end(ap); + + /* expand buffer if needed */ + if (total_size == 0) + return 0; + if (git_buf_grow(buf, buf->size + total_size + 1) < 0) + return -1; + + out = buf->ptr + buf->size; + + /* append separator to existing buf if needed */ + if (buf->size > 0 && out[-1] != separator) + *out++ = separator; + + va_start(ap, nbuf); + for (i = 0; i < nbuf; ++i) { + const char* segment; + size_t segment_len; + + segment = va_arg(ap, const char *); + 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_len > 0 && *segment == separator) { + segment++; + segment_len--; + } + + /* copy over next buffer */ + if (segment_len > 0) { + memmove(out, segment, segment_len); + out += segment_len; + } + + /* append trailing separator (except for last item) */ + if (i < nbuf - 1 && out > buf->ptr && out[-1] != separator) + *out++ = separator; + } + va_end(ap); + + /* set size based on num characters actually written */ + buf->size = out - buf->ptr; + buf->ptr[buf->size] = '\0'; + + return 0; +} + +int git_buf_join( + git_buf *buf, + char separator, + const char *str_a, + const char *str_b) +{ + size_t strlen_a = str_a ? strlen(str_a) : 0; + size_t strlen_b = strlen(str_b); + int need_sep = 0; + ssize_t offset_a = -1; + + /* not safe to have str_b point internally to the buffer */ + assert(str_b < buf->ptr || str_b > buf->ptr + buf->size); + + /* figure out if we need to insert a separator */ + if (separator && strlen_a) { + while (*str_b == separator) { str_b++; strlen_b--; } + if (str_a[strlen_a - 1] != separator) + need_sep = 1; + } + + /* str_a could be part of the buffer */ + if (str_a >= buf->ptr && str_a < buf->ptr + buf->size) + offset_a = str_a - buf->ptr; + + if (git_buf_grow(buf, strlen_a + strlen_b + need_sep + 1) < 0) + return -1; + + /* fix up internal pointers */ + if (offset_a >= 0) + str_a = buf->ptr + offset_a; + + /* do the actual copying */ + if (offset_a != 0) + memmove(buf->ptr, str_a, strlen_a); + if (need_sep) + buf->ptr[strlen_a] = separator; + memcpy(buf->ptr + strlen_a + need_sep, str_b, strlen_b); + + buf->size = strlen_a + strlen_b + need_sep; + buf->ptr[buf->size] = '\0'; + + return 0; +} + +void git_buf_rtrim(git_buf *buf) +{ + while (buf->size > 0) { + if (!git__isspace(buf->ptr[buf->size - 1])) + break; + + buf->size--; + } + + buf->ptr[buf->size] = '\0'; +} + +int git_buf_cmp(const git_buf *a, const git_buf *b) +{ + int result = memcmp(a->ptr, b->ptr, min(a->size, b->size)); + return (result != 0) ? result : + (a->size < b->size) ? -1 : (a->size > b->size) ? 1 : 0; +} + +int git_buf_common_prefix(git_buf *buf, const git_strarray *strings) +{ + size_t i; + const char *str, *pfx; + + git_buf_clear(buf); + + if (!strings || !strings->count) + return 0; + + /* initialize common prefix to first string */ + if (git_buf_sets(buf, strings->strings[0]) < 0) + return -1; + + /* go through the rest of the strings, truncating to shared prefix */ + for (i = 1; i < strings->count; ++i) { + + for (str = strings->strings[i], pfx = buf->ptr; + *str && *str == *pfx; str++, pfx++) + /* scanning */; + + git_buf_truncate(buf, pfx - buf->ptr); + + if (!buf->size) + break; + } + + return 0; +} + +bool git_buf_is_binary(const git_buf *buf) +{ + size_t i; + int printable = 0, nonprintable = 0; + + for (i = 0; i < buf->size; i++) { + unsigned char c = buf->ptr[i]; + if (c > 0x1F && c < 0x7F) + printable++; + else if (c == '\0') + return true; + else if (!git__isspace(c)) + nonprintable++; + } + + return ((printable >> 7) < nonprintable); +} + diff --git a/src/buffer.h b/src/buffer.h new file mode 100644 index 000000000..50c75f64e --- /dev/null +++ b/src/buffer.h @@ -0,0 +1,135 @@ +/* + * 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_buffer_h__ +#define INCLUDE_buffer_h__ + +#include "common.h" +#include <stdarg.h> + +typedef struct { + char *ptr; + size_t asize, size; +} git_buf; + +extern char git_buf__initbuf[]; +extern char git_buf__oom[]; + +#define GIT_BUF_INIT { git_buf__initbuf, 0, 0 } + +/** + * Initialize a git_buf structure. + * + * For the cases where GIT_BUF_INIT cannot be used to do static + * initialization. + */ +void git_buf_init(git_buf *buf, size_t initial_size); + +/** + * Grow the buffer to hold at least `target_size` bytes. + * + * 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 0 on success or -1 on failure + */ +int git_buf_grow(git_buf *buf, size_t target_size); + +/** + * Attempt to grow the buffer to hold at least `target_size` bytes. + * + * This is just like `git_buf_grow` except that even if the allocation + * fails, the git_buf will still be left in a valid state. + */ +int git_buf_try_grow(git_buf *buf, size_t target_size); + +void git_buf_free(git_buf *buf); +void git_buf_swap(git_buf *buf_a, git_buf *buf_b); +char *git_buf_detach(git_buf *buf); +void git_buf_attach(git_buf *buf, char *ptr, size_t asize); + +/** + * Test if there have been any reallocation failures with this git_buf. + * + * Any function that writes to a git_buf can fail due to memory allocation + * issues. If one fails, the git_buf will be marked with an OOM error and + * 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 false if no error, true if allocation error + */ +GIT_INLINE(bool) git_buf_oom(const git_buf *buf) +{ + return (buf->ptr == git_buf__oom); +} + +/* + * 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); +int git_buf_putc(git_buf *buf, char c); +int git_buf_put(git_buf *buf, const char *data, size_t len); +int git_buf_puts(git_buf *buf, const char *string); +int git_buf_printf(git_buf *buf, const char *format, ...) GIT_FORMAT_PRINTF(2, 3); +int git_buf_vprintf(git_buf *buf, const char *format, va_list ap); +void git_buf_clear(git_buf *buf); +void git_buf_consume(git_buf *buf, const char *end); +void git_buf_truncate(git_buf *buf, size_t len); +void git_buf_rtruncate_at_char(git_buf *path, char separator); + +int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...); +int git_buf_join(git_buf *buf, char separator, const char *str_a, const char *str_b); + +/** + * Join two strings as paths, inserting a slash between as needed. + * @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(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(ssize_t) git_buf_rfind_next(git_buf *buf, char ch) +{ + 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; +} + +/* Remove whitespace from the end of the buffer */ +void git_buf_rtrim(git_buf *buf); + +int git_buf_cmp(const git_buf *a, const git_buf *b); + +/* Fill buf with the common prefix of a array of strings */ +int git_buf_common_prefix(git_buf *buf, const git_strarray *strings); + +/* Check if buffer looks like it contains binary data */ +bool git_buf_is_binary(const git_buf *buf); + +#endif diff --git a/src/cache.c b/src/cache.c index 433fc3d9c..31da3c36e 100644 --- a/src/cache.c +++ b/src/cache.c @@ -1,63 +1,34 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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" #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) { - size_t i; - if (size < 8) size = 8; + size = git__size_t_powerof2(size); - /* round up size to closest power of 2 */ - size--; - size |= size >> 1; - size |= size >> 2; - size |= size >> 4; - size |= size >> 8; - size |= size >> 16; - - cache->size_mask = size; + cache->size_mask = size - 1; cache->lru_count = 0; cache->free_obj = free_ptr; - cache->nodes = git__malloc((size + 1) * sizeof(cache_node)); - if (cache->nodes == NULL) - return GIT_ENOMEM; + git_mutex_init(&cache->lock); - for (i = 0; i < (size + 1); ++i) { - git_mutex_init(&cache->nodes[i].lock); - cache->nodes[i].ptr = NULL; - } + cache->nodes = git__malloc(size * sizeof(git_cached_obj *)); + GITERR_CHECK_ALLOC(cache->nodes); - return GIT_SUCCESS; + memset(cache->nodes, 0x0, size * sizeof(git_cached_obj *)); + return 0; } void git_cache_free(git_cache *cache) @@ -65,65 +36,64 @@ void git_cache_free(git_cache *cache) size_t i; for (i = 0; i < (cache->size_mask + 1); ++i) { - if (cache->nodes[i].ptr) - git_cached_obj_decref(cache->nodes[i].ptr, cache->free_obj); - - git_mutex_free(&cache->nodes[i].lock); + if (cache->nodes[i] != NULL) + git_cached_obj_decref(cache->nodes[i], cache->free_obj); } - free(cache->nodes); + git__free(cache->nodes); } void *git_cache_get(git_cache *cache, const git_oid *oid) { - const uint32_t *hash; - cache_node *node = NULL; - void *result = NULL; + uint32_t hash; + git_cached_obj *node = NULL, *result = NULL; - hash = (const uint32_t *)oid->id; - node = &cache->nodes[hash[0] & cache->size_mask]; + memcpy(&hash, oid->id, sizeof(hash)); - git_mutex_lock(&node->lock); + git_mutex_lock(&cache->lock); { - if (node->ptr && git_cached_obj_compare(node->ptr, oid) == 0) { - git_cached_obj_incref(node->ptr); - result = node->ptr; + node = cache->nodes[hash & cache->size_mask]; + + if (node != NULL && git_oid_cmp(&node->oid, oid) == 0) { + git_cached_obj_incref(node); + result = node; } } - git_mutex_unlock(&node->lock); + git_mutex_unlock(&cache->lock); return result; } -void *git_cache_try_store(git_cache *cache, void *entry) +void *git_cache_try_store(git_cache *cache, void *_entry) { - const uint32_t *hash; - const git_oid *oid; - cache_node *node = NULL; + git_cached_obj *entry = _entry; + uint32_t hash; - oid = &((git_cached_obj*)entry)->oid; - hash = (const uint32_t *)oid->id; - node = &cache->nodes[hash[0] & cache->size_mask]; + memcpy(&hash, &entry->oid, sizeof(hash)); /* increase the refcount on this object, because * the cache now owns it */ git_cached_obj_incref(entry); - git_mutex_lock(&node->lock); - - if (node->ptr == NULL) { - node->ptr = entry; - } else if (git_cached_obj_compare(node->ptr, oid) == 0) { - git_cached_obj_decref(entry, cache->free_obj); - entry = node->ptr; - } else { - git_cached_obj_decref(node->ptr, cache->free_obj); - node->ptr = entry; + + git_mutex_lock(&cache->lock); + { + git_cached_obj *node = cache->nodes[hash & cache->size_mask]; + + if (node == NULL) { + cache->nodes[hash & cache->size_mask] = entry; + } else if (git_oid_cmp(&node->oid, &entry->oid) == 0) { + git_cached_obj_decref(entry, cache->free_obj); + entry = node; + } else { + git_cached_obj_decref(node, cache->free_obj); + cache->nodes[hash & cache->size_mask] = entry; + } } + git_mutex_unlock(&cache->lock); /* increase the refcount again, because we are * returning it to the user */ git_cached_obj_incref(entry); - git_mutex_unlock(&node->lock); return entry; } diff --git a/src/cache.h b/src/cache.h index 4794dea3a..6dc706897 100644 --- a/src/cache.h +++ b/src/cache.h @@ -1,3 +1,9 @@ +/* + * 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_cache_h__ #define INCLUDE_cache_h__ @@ -17,42 +23,32 @@ typedef struct { } git_cached_obj; typedef struct { - git_cached_obj *ptr; + git_cached_obj **nodes; git_mutex lock; -} cache_node; - -typedef struct { - cache_node *nodes; unsigned int lru_count; size_t size_mask; git_cached_obj_freeptr free_obj; } git_cache; - int git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_ptr); void git_cache_free(git_cache *cache); void *git_cache_try_store(git_cache *cache, void *entry); void *git_cache_get(git_cache *cache, const git_oid *oid); - -GIT_INLINE(int) git_cached_obj_compare(git_cached_obj *obj, const git_oid *oid) -{ - return git_oid_cmp(&obj->oid, oid); -} - -GIT_INLINE(void) git_cached_obj_incref(git_cached_obj *obj) +GIT_INLINE(void) git_cached_obj_incref(void *_obj) { + git_cached_obj *obj = _obj; git_atomic_inc(&obj->refcount); } -GIT_INLINE(void) git_cached_obj_decref(git_cached_obj *obj, git_cached_obj_freeptr free_obj) +GIT_INLINE(void) git_cached_obj_decref(void *_obj, git_cached_obj_freeptr free_obj) { + git_cached_obj *obj = _obj; + if (git_atomic_dec(&obj->refcount) == 0) free_obj(obj); } - - #endif diff --git a/src/cc-compat.h b/src/cc-compat.h index cf6cccf12..9f23dcae2 100644 --- a/src/cc-compat.h +++ b/src/cc-compat.h @@ -1,5 +1,8 @@ /* - * cc-compat.h - C compiler compat macros for internal use + * 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_compat_h__ #define INCLUDE_compat_h__ @@ -8,66 +11,59 @@ * See if our compiler is known to support flexible array members. */ #ifndef GIT_FLEX_ARRAY -# if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) -# define GIT_FLEX_ARRAY /* empty */ -# elif defined(__GNUC__) -# if (__GNUC__ >= 3) -# define GIT_FLEX_ARRAY /* empty */ -# else -# define GIT_FLEX_ARRAY 0 /* older GNU extension */ -# endif -# endif +# if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +# define GIT_FLEX_ARRAY /* empty */ +# elif defined(__GNUC__) +# if (__GNUC__ >= 3) +# define GIT_FLEX_ARRAY /* empty */ +# else +# define GIT_FLEX_ARRAY 0 /* older GNU extension */ +# endif +# endif /* Default to safer but a bit wasteful traditional style */ -# ifndef GIT_FLEX_ARRAY -# define GIT_FLEX_ARRAY 1 -# endif +# ifndef GIT_FLEX_ARRAY +# define GIT_FLEX_ARRAY 1 +# endif #endif #ifdef __GNUC__ -# define GIT_TYPEOF(x) (__typeof__(x)) +# define GIT_TYPEOF(x) (__typeof__(x)) #else -# define GIT_TYPEOF(x) +# define GIT_TYPEOF(x) #endif -#ifdef __cplusplus -# define GIT_UNUSED(x) -#else -# ifdef __GNUC__ -# define GIT_UNUSED(x) x __attribute__ ((__unused__)) -# else -# define GIT_UNUSED(x) x -# endif -#endif - -#if defined(_MSC_VER) -#define GIT_UNUSED_ARG(x) ((void)(x)); /* note trailing ; */ -#else -#define GIT_UNUSED_ARG(x) -#endif - -/* - * Does our compiler/platform support the C99 <inttypes.h> and - * <stdint.h> header files. (C99 requires that <inttypes.h> - * includes <stdint.h>). - */ -#if !defined(_MSC_VER) -# define GIT_HAVE_INTTYPES_H 1 -#endif +#define GIT_UNUSED(x) ((void)(x)) /* Define the printf format specifer to use for size_t output */ #if defined(_MSC_VER) || defined(__MINGW32__) -# define PRIuZ "Iu" +# define PRIuZ "Iu" #else -# define PRIuZ "zu" +# define PRIuZ "zu" #endif /* Micosoft Visual C/C++ */ #if defined(_MSC_VER) /* disable "deprecated function" warnings */ -# pragma warning ( disable : 4996 ) +# pragma warning ( disable : 4996 ) /* disable "conditional expression is constant" level 4 warnings */ -# pragma warning ( disable : 4127 ) +# pragma warning ( disable : 4127 ) +#endif + +#if defined (_MSC_VER) + typedef unsigned char bool; +# define true 1 +# define false 0 +#else +# include <stdbool.h> +#endif + +#ifndef va_copy +# ifdef __va_copy +# define va_copy(dst, src) __va_copy(dst, src) +# else +# define va_copy(dst, src) ((dst) = (src)) +# endif #endif #endif /* INCLUDE_compat_h__ */ diff --git a/src/commit.c b/src/commit.c index bfae0592e..2f40dc67d 100644 --- a/src/commit.c +++ b/src/commit.c @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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 "git2/common.h" @@ -32,25 +14,17 @@ #include "odb.h" #include "commit.h" #include "signature.h" +#include "message.h" #include <stdarg.h> -#define COMMIT_BASIC_PARSE 0x0 -#define COMMIT_FULL_PARSE 0x1 - -#define COMMIT_PRINT(commit) {\ - char oid[41]; oid[40] = 0;\ - git_oid_fmt(oid, &commit->object.id);\ - printf("Oid: %s | In degree: %d | Time: %u\n", oid, commit->in_degree, commit->commit_time);\ -} - static void clear_parents(git_commit *commit) { unsigned int i; for (i = 0; i < commit->parent_oids.length; ++i) { git_oid *parent = git_vector_get(&commit->parent_oids, i); - free(parent); + git__free(parent); } git_vector_clear(&commit->parent_oids); @@ -64,9 +38,9 @@ void git_commit__free(git_commit *commit) git_signature_free(commit->author); git_signature_free(commit->committer); - free(commit->message); - free(commit->message_short); - free(commit); + git__free(commit->message); + git__free(commit->message_encoding); + git__free(commit); } const git_oid *git_commit_id(git_commit *c) @@ -74,97 +48,97 @@ const git_oid *git_commit_id(git_commit *c) return git_object_id((git_object *)c); } - int git_commit_create_v( git_oid *oid, git_repository *repo, const char *update_ref, const git_signature *author, const git_signature *committer, + const char *message_encoding, const char *message, - const git_oid *tree_oid, + const git_tree *tree, int parent_count, ...) { va_list ap; - int i, error; - const git_oid **oids; + int i, res; + const git_commit **parents; - oids = git__malloc(parent_count * sizeof(git_oid *)); + parents = git__malloc(parent_count * sizeof(git_commit *)); + GITERR_CHECK_ALLOC(parents); va_start(ap, parent_count); for (i = 0; i < parent_count; ++i) - oids[i] = va_arg(ap, const git_oid *); + parents[i] = va_arg(ap, const git_commit *); va_end(ap); - error = git_commit_create( - oid, repo, update_ref, author, committer, message, - tree_oid, parent_count, oids); - - free((void *)oids); + res = git_commit_create( + oid, repo, update_ref, author, committer, + message_encoding, message, + tree, parent_count, parents); - return error; + git__free((void *)parents); + return res; } -int git_commit_create_ov( - git_oid *oid, - git_repository *repo, - const char *update_ref, - const git_signature *author, - const git_signature *committer, - const char *message, - const git_tree *tree, - int parent_count, - ...) +/* 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) { - va_list ap; - int i, error; - const git_oid **oids; + git_reference *ref; + int res; - oids = git__malloc(parent_count * sizeof(git_oid *)); + res = git_reference_lookup(&ref, repo, ref_name); - va_start(ap, parent_count); - for (i = 0; i < parent_count; ++i) - oids[i] = git_object_id(va_arg(ap, const git_object *)); - va_end(ap); + /* 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); + } - error = git_commit_create( - oid, repo, update_ref, author, committer, message, - git_object_id((git_object *)tree), - parent_count, oids); + if (res < 0) + return -1; - free((void *)oids); + /* 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; - return error; -} + /* The target pointed at by this reference */ + sym_target = git_reference_target(ref); -int git_commit_create_o( - git_oid *oid, - git_repository *repo, - const char *update_ref, - const git_signature *author, - const git_signature *committer, - const char *message, - const git_tree *tree, - int parent_count, - const git_commit *parents[]) -{ - int i, error; - const git_oid **oids; + /* resolve the reference to the target it points to */ + res = git_reference_resolve(&aux, ref); + + /* + * 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; + } - oids = git__malloc(parent_count * sizeof(git_oid *)); + /* free the original symbolic reference now; not before because + * we're using the `sym_target` pointer */ + git_reference_free(ref); - for (i = 0; i < parent_count; ++i) - oids[i] = git_object_id((git_object *)parents[i]); + if (res < 0) + return -1; - error = git_commit_create( - oid, repo, update_ref, author, committer, message, - git_object_id((git_object *)tree), - parent_count, oids); - - free((void *)oids); + /* store the newly found direct reference in its place */ + ref = aux; + } - return error; + /* 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( @@ -173,150 +147,137 @@ int git_commit_create( const char *update_ref, const git_signature *author, const git_signature *committer, + const char *message_encoding, const char *message, - const git_oid *tree_oid, + const git_tree *tree, int parent_count, - const git_oid *parents[]) + const git_commit *parents[]) { - size_t final_size = 0; - int message_length, author_length, committer_length; + git_buf commit = GIT_BUF_INIT, cleaned_message = GIT_BUF_INIT; + int i; + git_odb *odb; - char *author_str, *committer_str; + assert(git_object_owner((const git_object *)tree) == repo); - int error, i; - git_odb_stream *stream; + git_oid__writebuf(&commit, "tree ", git_object_id((const git_object *)tree)); - message_length = strlen(message); - author_length = git_signature__write(&author_str, "author", author); - committer_length = git_signature__write(&committer_str, "committer", committer); - - if (author_length < 0 || committer_length < 0) - return git__throw(GIT_EINVALIDARGS, "Cannot create commit. Failed to parse signature"); + for (i = 0; i < parent_count; ++i) { + assert(git_object_owner((const git_object *)parents[i]) == repo); + git_oid__writebuf(&commit, "parent ", git_object_id((const git_object *)parents[i])); + } - final_size += GIT_OID_LINE_LENGTH("tree"); - final_size += GIT_OID_LINE_LENGTH("parent") * parent_count; - final_size += author_length; - final_size += committer_length; - final_size += 1 + message_length; + git_signature__writebuf(&commit, "author ", author); + git_signature__writebuf(&commit, "committer ", committer); - if ((error = git_odb_open_wstream(&stream, repo->db, final_size, GIT_OBJ_COMMIT)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to create commit"); + if (message_encoding != NULL) + git_buf_printf(&commit, "encoding %s\n", message_encoding); - git__write_oid(stream, "tree", tree_oid); + git_buf_putc(&commit, '\n'); - for (i = 0; i < parent_count; ++i) - git__write_oid(stream, "parent", parents[i]); + /* Remove comments by default */ + if (git_message_prettify(&cleaned_message, message, 1) < 0) + goto on_error; - stream->write(stream, author_str, author_length); - free(author_str); + if (git_buf_puts(&commit, git_buf_cstr(&cleaned_message)) < 0) + goto on_error; - stream->write(stream, committer_str, committer_length); - free(committer_str); + git_buf_free(&cleaned_message); + if (git_repository_odb__weakptr(&odb, repo) < 0) + goto on_error; - stream->write(stream, "\n", 1); - stream->write(stream, message, message_length); + if (git_odb_write(oid, odb, commit.ptr, commit.size, GIT_OBJ_COMMIT) < 0) + goto on_error; - error = stream->finalize_write(oid, stream); - stream->free(stream); + git_buf_free(&commit); - if (error == GIT_SUCCESS && update_ref != NULL) { - git_reference *head; + if (update_ref != NULL) + return update_reference(repo, oid, update_ref); - error = git_reference_lookup(&head, repo, update_ref); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to create commit"); + return 0; - error = git_reference_resolve(&head, head); - if (error < GIT_SUCCESS) { - if (error != GIT_ENOTFOUND) - 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. - */ - return git_reference_create_oid_f(&head, repo, git_reference_target(head), oid); - } - - error = git_reference_set_oid(head, oid); - } - - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to create commit"); - - return GIT_SUCCESS; +on_error: + git_buf_free(&commit); + git_buf_free(&cleaned_message); + giterr_set(GITERR_OBJECT, "Failed to create commit."); + return -1; } -int commit_parse_buffer(git_commit *commit, const void *data, size_t len) +int git_commit__parse_buffer(git_commit *commit, const void *data, size_t len) { - const char *buffer = (char *)data; - const char *buffer_end = (char *)data + len; + const char *buffer = data; + 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__parse_oid(&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__parse_oid(&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 ")) < GIT_SUCCESS) - return git__rethrow(error, "Failed to parse buffer"); + 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 ")) < GIT_SUCCESS) - return git__rethrow(error, "Failed to parse buffer"); + GITERR_CHECK_ALLOC(commit->committer); - /* parse commit message */ - while (buffer <= buffer_end && *buffer == '\n') - buffer++; + if (git_signature__parse(commit->committer, &buffer, buffer_end, "committer ", '\n') < 0) + return -1; - if (buffer < buffer_end) { - const char *line_end; - size_t message_len; + if (git__prefixcmp(buffer, "encoding ") == 0) { + const char *encoding_end; + buffer += strlen("encoding "); - /* Long message */ - message_len = buffer_end - buffer; - commit->message = git__malloc(message_len + 1); - memcpy(commit->message, buffer, message_len); - commit->message[message_len] = 0; + encoding_end = buffer; + while (encoding_end < buffer_end && *encoding_end != '\n') + encoding_end++; - /* Short message */ - if((line_end = memchr(buffer, '\n', buffer_end - buffer)) == NULL) - line_end = buffer_end; - message_len = line_end - buffer; + commit->message_encoding = git__strndup(buffer, encoding_end - buffer); + GITERR_CHECK_ALLOC(commit->message_encoding); - commit->message_short = git__malloc(message_len + 1); - memcpy(commit->message_short, buffer, message_len); - commit->message_short[message_len] = 0; + buffer = encoding_end; } - return GIT_SUCCESS; + /* parse commit message */ + while (buffer < buffer_end - 1 && *buffer == '\n') + buffer++; + + if (buffer <= buffer_end) { + commit->message = git__strndup(buffer, buffer_end - buffer); + GITERR_CHECK_ALLOC(commit->message); + } + + 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) { assert(commit); - return commit_parse_buffer(commit, obj->raw.data, obj->raw.len); + return git_commit__parse_buffer(commit, obj->raw.data, obj->raw.len); } #define GIT_COMMIT_GETTER(_rvalue, _name, _return) \ @@ -329,7 +290,7 @@ int git_commit__parse(git_commit *commit, git_odb_object *obj) GIT_COMMIT_GETTER(const git_signature *, author, commit->author) GIT_COMMIT_GETTER(const git_signature *, committer, commit->committer) GIT_COMMIT_GETTER(const char *, message, commit->message) -GIT_COMMIT_GETTER(const char *, message_short, commit->message_short) +GIT_COMMIT_GETTER(const char *, message_encoding, commit->message_encoding) GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time) GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset) GIT_COMMIT_GETTER(unsigned int, parentcount, commit->parent_oids.length) @@ -348,8 +309,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/commit.h b/src/commit.h index 3d15c5044..d9f492862 100644 --- a/src/commit.h +++ b/src/commit.h @@ -1,3 +1,9 @@ +/* + * 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_commit_h__ #define INCLUDE_commit_h__ @@ -17,11 +23,12 @@ struct git_commit { git_signature *author; git_signature *committer; + char *message_encoding; char *message; - char *message_short; }; void git_commit__free(git_commit *c); int git_commit__parse(git_commit *commit, git_odb_object *obj); +int git_commit__parse_buffer(git_commit *commit, const void *data, size_t len); #endif diff --git a/src/common.h b/src/common.h index f4f11fd2f..e2a300291 100644 --- a/src/common.h +++ b/src/common.h @@ -1,19 +1,15 @@ +/* + * 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_common_h__ #define INCLUDE_common_h__ -/** Force 64 bit off_t size on POSIX. */ -#define _FILE_OFFSET_BITS 64 - -#if defined(_WIN32) && !defined(__CYGWIN__) -#define GIT_WIN32 1 -#endif - -#include "git2/thread-utils.h" +#include "git2/common.h" #include "cc-compat.h" -#ifdef GIT_HAVE_INTTYPES_H -# include <inttypes.h> -#endif #include <assert.h> #include <errno.h> #include <limits.h> @@ -29,35 +25,47 @@ # include <io.h> # include <direct.h> # include <windows.h> -# include "msvc-compat.h" -# include "mingw-compat.h" +# include "win32/msvc-compat.h" +# include "win32/mingw-compat.h" # ifdef GIT_THREADS -# include "win32/pthread.h" +# include "win32/pthread.h" #endif # define snprintf _snprintf -typedef SSIZE_T ssize_t; - #else # include <unistd.h> -# include <arpa/inet.h> # ifdef GIT_THREADS -# include <pthread.h> +# include <pthread.h> # endif #endif -#include "git2/common.h" #include "git2/types.h" #include "git2/errors.h" #include "thread-utils.h" #include "bswap.h" -#define GIT_PATH_MAX 4096 -extern int git__throw(int error, const char *, ...) GIT_FORMAT_PRINTF(2, 3); -extern int git__rethrow(int error, const char *, ...) GIT_FORMAT_PRINTF(2, 3); +#include <regex.h> + +extern void git___throw(const char *, ...) GIT_FORMAT_PRINTF(1, 2); +#define git__throw(error, ...) \ + (git___throw(__VA_ARGS__), error) + +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" + #endif /* INCLUDE_common_h__ */ diff --git a/src/compat/fnmatch.c b/src/compat/fnmatch.c new file mode 100644 index 000000000..835d811bc --- /dev/null +++ b/src/compat/fnmatch.c @@ -0,0 +1,180 @@ +/* + * 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. + */ + +/* + * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6. + * Compares a filename or pathname to a pattern. + */ + +#include <ctype.h> +#include <stdio.h> +#include <string.h> + +#include "fnmatch.h" + +#define EOS '\0' + +#define RANGE_MATCH 1 +#define RANGE_NOMATCH 0 +#define RANGE_ERROR (-1) + +static int rangematch(const char *, char, int, char **); + +int +p_fnmatch(const char *pattern, const char *string, int flags) +{ + const char *stringstart; + char *newp; + char c, test; + + for (stringstart = string;;) + switch (c = *pattern++) { + case EOS: + if ((flags & FNM_LEADING_DIR) && *string == '/') + return (0); + return (*string == EOS ? 0 : FNM_NOMATCH); + case '?': + if (*string == EOS) + return (FNM_NOMATCH); + if (*string == '/' && (flags & FNM_PATHNAME)) + return (FNM_NOMATCH); + if (*string == '.' && (flags & FNM_PERIOD) && + (string == stringstart || + ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) + return (FNM_NOMATCH); + ++string; + break; + case '*': + c = *pattern; + /* Collapse multiple stars. */ + while (c == '*') + c = *++pattern; + + if (*string == '.' && (flags & FNM_PERIOD) && + (string == stringstart || + ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) + return (FNM_NOMATCH); + + /* Optimize for pattern with * at end or before /. */ + if (c == EOS) { + if (flags & FNM_PATHNAME) + return ((flags & FNM_LEADING_DIR) || + strchr(string, '/') == NULL ? + 0 : FNM_NOMATCH); + else + return (0); + } else if (c == '/' && (flags & FNM_PATHNAME)) { + if ((string = strchr(string, '/')) == NULL) + return (FNM_NOMATCH); + break; + } + + /* General case, use recursion. */ + while ((test = *string) != EOS) { + if (!p_fnmatch(pattern, string, flags & ~FNM_PERIOD)) + return (0); + if (test == '/' && (flags & FNM_PATHNAME)) + break; + ++string; + } + return (FNM_NOMATCH); + case '[': + if (*string == EOS) + return (FNM_NOMATCH); + if (*string == '/' && (flags & FNM_PATHNAME)) + return (FNM_NOMATCH); + if (*string == '.' && (flags & FNM_PERIOD) && + (string == stringstart || + ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) + return (FNM_NOMATCH); + + switch (rangematch(pattern, *string, flags, &newp)) { + case RANGE_ERROR: + /* not a good range, treat as normal text */ + goto normal; + case RANGE_MATCH: + pattern = newp; + break; + case RANGE_NOMATCH: + return (FNM_NOMATCH); + } + ++string; + break; + case '\\': + if (!(flags & FNM_NOESCAPE)) { + if ((c = *pattern++) == EOS) { + c = '\\'; + --pattern; + } + } + /* FALLTHROUGH */ + default: + normal: + if (c != *string && !((flags & FNM_CASEFOLD) && + (tolower((unsigned char)c) == + tolower((unsigned char)*string)))) + return (FNM_NOMATCH); + ++string; + break; + } + /* NOTREACHED */ +} + +static int +rangematch(const char *pattern, char test, int flags, char **newp) +{ + int negate, ok; + char c, c2; + + /* + * A bracket expression starting with an unquoted circumflex + * character produces unspecified results (IEEE 1003.2-1992, + * 3.13.2). This implementation treats it like '!', for + * consistency with the regular expression syntax. + * J.T. Conklin (conklin@ngai.kaleida.com) + */ + if ((negate = (*pattern == '!' || *pattern == '^')) != 0) + ++pattern; + + if (flags & FNM_CASEFOLD) + test = (char)tolower((unsigned char)test); + + /* + * A right bracket shall lose its special meaning and represent + * itself in a bracket expression if it occurs first in the list. + * -- POSIX.2 2.8.3.2 + */ + ok = 0; + c = *pattern++; + do { + if (c == '\\' && !(flags & FNM_NOESCAPE)) + c = *pattern++; + if (c == EOS) + return (RANGE_ERROR); + if (c == '/' && (flags & FNM_PATHNAME)) + return (RANGE_NOMATCH); + if ((flags & FNM_CASEFOLD)) + c = (char)tolower((unsigned char)c); + if (*pattern == '-' + && (c2 = *(pattern+1)) != EOS && c2 != ']') { + pattern += 2; + if (c2 == '\\' && !(flags & FNM_NOESCAPE)) + c2 = *pattern++; + if (c2 == EOS) + return (RANGE_ERROR); + if (flags & FNM_CASEFOLD) + c2 = (char)tolower((unsigned char)c2); + if (c <= test && test <= c2) + ok = 1; + } else if (c == test) + ok = 1; + } while ((c = *pattern++) != ']'); + + *newp = (char *)pattern; + return (ok == negate ? RANGE_NOMATCH : RANGE_MATCH); +} + diff --git a/src/compat/fnmatch.h b/src/compat/fnmatch.h new file mode 100644 index 000000000..7faef09b3 --- /dev/null +++ b/src/compat/fnmatch.h @@ -0,0 +1,27 @@ +/* + * 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_fnmatch__compat_h__ +#define INCLUDE_fnmatch__compat_h__ + +#include "common.h" + +#define FNM_NOMATCH 1 /* Match failed. */ +#define FNM_NOSYS 2 /* Function not supported (unused). */ + +#define FNM_NOESCAPE 0x01 /* Disable backslash escaping. */ +#define FNM_PATHNAME 0x02 /* Slash must be matched by slash. */ +#define FNM_PERIOD 0x04 /* Period must be matched by period. */ +#define FNM_LEADING_DIR 0x08 /* Ignore /<tail> after Imatch. */ +#define FNM_CASEFOLD 0x10 /* Case insensitive search. */ + +#define FNM_IGNORECASE FNM_CASEFOLD +#define FNM_FILE_NAME FNM_PATHNAME + +extern int p_fnmatch(const char *pattern, const char *string, int flags); + +#endif /* _FNMATCH_H */ + diff --git a/src/config.c b/src/config.c index 29b9b799f..d18b85c30 100644 --- a/src/config.c +++ b/src/config.c @@ -1,34 +1,18 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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 "fileops.h" -#include "hashtable.h" #include "config.h" #include "git2/config.h" #include "vector.h" +#if GIT_WIN32 +# include <windows.h> +#endif #include <ctype.h> @@ -37,55 +21,7 @@ typedef struct { int priority; } file_internal; -int git_config_open_file(git_config **out, const char *path) -{ - git_config_file *file = NULL; - git_config *cfg = NULL; - int error = GIT_SUCCESS; - - error = git_config_new(&cfg); - if (error < GIT_SUCCESS) - return error; - - error = git_config_file__ondisk(&file, path); - if (error < GIT_SUCCESS) { - git_config_free(cfg); - return error; - } - - error = git_config_add_file(cfg, file, 1); - if (error < GIT_SUCCESS) { - file->free(file); - git_config_free(cfg); - return error; - } - - error = file->open(file); - if (error < GIT_SUCCESS) { - git_config_free(cfg); - return git__rethrow(error, "Failed to open config file"); - } - - *out = cfg; - - return GIT_SUCCESS; -} - -int git_config_open_global(git_config **out) -{ - char full_path[GIT_PATH_MAX]; - const char *home; - - home = getenv("HOME"); - if (home == NULL) - return git__throw(GIT_EOSERR, "Failed to open global config file. Cannot find $HOME variable"); - - git__joinpath(full_path, home, GIT_CONFIG_FILENAME); - - return git_config_open_file(out, full_path); -} - -void git_config_free(git_config *cfg) +static void config_free(git_config *cfg) { unsigned int i; git_config_file *file; @@ -95,17 +31,25 @@ void git_config_free(git_config *cfg) internal = git_vector_get(&cfg->files, i); file = internal->file; file->free(file); - free(internal); + git__free(internal); } git_vector_free(&cfg->files); - free(cfg); + git__free(cfg); +} + +void git_config_free(git_config *cfg) +{ + if (cfg == NULL) + return; + + GIT_REFCOUNT_DEC(cfg, config_free); } static int config_backend_cmp(const void *a, const void *b) { - const file_internal *bk_a = *(const file_internal **)(a); - const file_internal *bk_b = *(const file_internal **)(b); + const file_internal *bk_a = (const file_internal *)(a); + const file_internal *bk_b = (const file_internal *)(b); return bk_b->priority - bk_a->priority; } @@ -115,52 +59,86 @@ int git_config_new(git_config **out) git_config *cfg; cfg = git__malloc(sizeof(git_config)); - if (cfg == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(cfg); memset(cfg, 0x0, sizeof(git_config)); if (git_vector_init(&cfg->files, 3, config_backend_cmp) < 0) { - free(cfg); - return GIT_ENOMEM; + git__free(cfg); + return -1; } *out = cfg; + GIT_REFCOUNT_INC(cfg); + return 0; +} + +int git_config_add_file_ondisk(git_config *cfg, const char *path, int priority) +{ + git_config_file *file = NULL; + + if (git_config_file__ondisk(&file, path) < 0) + return -1; + + 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 -1; + } + + return 0; +} + +int git_config_open_ondisk(git_config **cfg, const char *path) +{ + if (git_config_new(cfg) < 0) + return -1; - return GIT_SUCCESS; + if (git_config_add_file_ondisk(*cfg, path, 1) < 0) { + git_config_free(*cfg); + return -1; + } + + return 0; } int git_config_add_file(git_config *cfg, git_config_file *file, int priority) { file_internal *internal; + int result; assert(cfg && 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) { - free(internal); - return GIT_ENOMEM; + git__free(internal); + return -1; } git_vector_sort(&cfg->files); internal->file->cfg = cfg; - return GIT_SUCCESS; + return 0; } /* * Loop over all the variables */ -int git_config_foreach(git_config *cfg, int (*fn)(const char *, void *), void *data) +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; @@ -174,25 +152,33 @@ int git_config_foreach(git_config *cfg, int (*fn)(const char *, void *), void *d return ret; } +int git_config_delete(git_config *cfg, const char *name) +{ + file_internal *internal; + git_config_file *file; + + assert(cfg->files.length); + + internal = git_vector_get(&cfg->files, 0); + file = internal->file; + + return file->del(file, name); +} /************** * Setters **************/ -/* - * Internal function to actually set the string value of a variable - */ - -int git_config_set_long(git_config *cfg, const char *name, long int value) +int git_config_set_int64(git_config *cfg, const char *name, int64_t value) { char str_value[32]; /* All numbers should fit in here */ - snprintf(str_value, sizeof(str_value), "%ld", value); + p_snprintf(str_value, sizeof(str_value), "%" PRId64, value); return git_config_set_string(cfg, name, str_value); } -int git_config_set_int(git_config *cfg, const char *name, int value) +int git_config_set_int32(git_config *cfg, const char *name, int32_t value) { - return git_config_set_long(cfg, name, value); + return git_config_set_int64(cfg, name, (int64_t)value); } int git_config_set_bool(git_config *cfg, const char *name, int value) @@ -205,8 +191,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; @@ -214,109 +199,309 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value) return file->set(file, name, value); } -/*********** - * Getters - ***********/ - -int git_config_get_long(git_config *cfg, const char *name, long int *out) +static int parse_int64(int64_t *out, const char *value) { - const char *value, *num_end; - int ret; - long int num; - - ret = git_config_get_string(cfg, name, &value); - if (ret < GIT_SUCCESS) - return git__rethrow(ret, "Failed to get value for %s", name); + const char *num_end; + int64_t num; - ret = git__strtol32(&num, value, &num_end, 0); - if (ret < GIT_SUCCESS) - return git__rethrow(ret, "Failed to get value for %s", name); + if (git__strtol64(&num, value, &num_end, 0) < 0) + return -1; switch (*num_end) { - case '\0': - break; - case 'k': - case 'K': + case 'g': + case 'G': num *= 1024; - break; + /* fallthrough */ + case 'm': case 'M': - num *= 1024 * 1024; - break; - case 'g': - case 'G': - num *= 1024 * 1024 * 1024; - break; + num *= 1024; + /* fallthrough */ + + case 'k': + case 'K': + num *= 1024; + + /* check that that there are no more characters after the + * given modifier suffix */ + if (num_end[1] != '\0') + return -1; + + /* fallthrough */ + + case '\0': + *out = num; + return 0; + default: - return git__throw(GIT_EINVALIDTYPE, "Failed to get value for %s. Value is of invalid type", name); + return -1; } +} - *out = num; +static int parse_int32(int32_t *out, const char *value) +{ + int64_t tmp; + int32_t truncate; - return GIT_SUCCESS; + if (parse_int64(&tmp, value) < 0) + return -1; + + truncate = tmp & 0xFFFFFFFF; + if (truncate != tmp) + return -1; + + *out = truncate; + return 0; } -int git_config_get_int(git_config *cfg, const char *name, int *out) +/*********** + * Getters + ***********/ +int git_config_lookup_map_value( + git_cvar_map *maps, size_t map_n, const char *value, int *out) { - long int tmp; + size_t i; + + 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; + + if (git__parse_bool(&bool_val, value) == 0 && + bool_val == (int)m->cvar_type) { + *out = m->map_value; + return 0; + } + break; + } + + case GIT_CVAR_INT32: + if (parse_int32(out, value) == 0) + return 0; + break; + + case GIT_CVAR_STRING: + if (strcasecmp(value, m->str_match) == 0) { + *out = m->map_value; + return 0; + } + break; + } + } + + return GIT_ENOTFOUND; +} + +int git_config_get_mapped( + int *out, + git_config *cfg, + const char *name, + git_cvar_map *maps, + size_t map_n) +{ + const char *value; int ret; - ret = git_config_get_long(cfg, name, &tmp); + ret = git_config_get_string(&value, cfg, name); + if (ret < 0) + return ret; - *out = (int) tmp; + if (!git_config_lookup_map_value(maps, map_n, value, out)) + return 0; - return ret; + giterr_set(GITERR_CONFIG, + "Failed to map the '%s' config variable with a valid value", name); + return -1; } -int git_config_get_bool(git_config *cfg, const char *name, int *out) +int git_config_get_int64(int64_t *out, git_config *cfg, const char *name) { 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(&value, cfg, name); + if (ret < 0) + return ret; - /* A missing value means true */ - if (value == NULL) { - *out = 1; - return GIT_SUCCESS; + if (parse_int64(out, value) < 0) { + giterr_set(GITERR_CONFIG, "Failed to parse '%s' as an integer", value); + return -1; } - if (!strcasecmp(value, "true") || - !strcasecmp(value, "yes") || - !strcasecmp(value, "on")) { - *out = 1; - return GIT_SUCCESS; - } - if (!strcasecmp(value, "false") || - !strcasecmp(value, "no") || - !strcasecmp(value, "off")) { - *out = 0; - return GIT_SUCCESS; + return 0; +} + +int git_config_get_int32(int32_t *out, git_config *cfg, const char *name) +{ + const char *value; + int ret; + + ret = git_config_get_string(&value, cfg, name); + 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; } - /* Try to parse it as an integer */ - error = git_config_get_int(cfg, name, out); - if (error == GIT_SUCCESS) + return 0; +} + +int git_config_get_bool(int *out, git_config *cfg, const char *name) +{ + const char *value; + int ret; + + ret = git_config_get_string(&value, cfg, name); + if (ret < 0) + return ret; + + if (git__parse_bool(out, value) == 0) + return 0; + + if (parse_int32(out, value) == 0) { *out = !!(*out); + return 0; + } - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to get value for %s", name); - return error; + giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a boolean value", value); + return -1; +} + +int git_config_get_string(const char **out, git_config *cfg, const char *name) +{ + file_internal *internal; + unsigned int i; + + assert(cfg->files.length); + + *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_ENOTFOUND; } -int git_config_get_string(git_config *cfg, const char *name, const char **out) +int git_config_get_multivar(git_config *cfg, const char *name, const char *regexp, + int (*fn)(const char *value, void *data), void *data) { file_internal *internal; git_config_file *file; + 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); - internal = git_vector_get(&cfg->files, 0); - file = internal->file; + /* + * This loop runs the "wrong" way 'round because we need to + * look at every value from the most general to most specific + */ + for (i = cfg->files.length; i > 0; --i) { + internal = git_vector_get(&cfg->files, i - 1); + file = internal->file; + ret = file->get_multivar(file, name, regexp, fn, data); + if (ret < 0 && ret != GIT_ENOTFOUND) + return ret; + } + + 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 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; + ret = file->set_multivar(file, name, regexp, value); + if (ret < 0 && ret != GIT_ENOTFOUND) + return ret; + } + + return 0; +} + +int git_config_find_global_r(git_buf *path) +{ + return git_futils_find_global_file(path, GIT_CONFIG_FILENAME); +} - return file->get(file, name, out); +int git_config_find_global(char *global_config_path, size_t length) +{ + git_buf path = GIT_BUF_INIT; + int ret = git_config_find_global_r(&path); + + if (ret < 0) { + git_buf_free(&path); + return ret; + } + + if (path.size >= length) { + git_buf_free(&path); + giterr_set(GITERR_NOMEMORY, + "Path is to long to fit on the given buffer"); + return -1; + } + + git_buf_copy_cstr(global_config_path, length, &path); + git_buf_free(&path); + return 0; +} + +int git_config_find_system_r(git_buf *path) +{ + return git_futils_find_system_file(path, GIT_CONFIG_FILENAME_SYSTEM); +} + +int git_config_find_system(char *system_config_path, size_t length) +{ + git_buf path = GIT_BUF_INIT; + int ret = git_config_find_system_r(&path); + + if (ret < 0) { + git_buf_free(&path); + return ret; + } + + if (path.size >= length) { + git_buf_free(&path); + giterr_set(GITERR_NOMEMORY, + "Path is to long to fit on the given buffer"); + return -1; + } + + git_buf_copy_cstr(system_config_path, length, &path); + git_buf_free(&path); + return 0; +} + +int git_config_open_global(git_config **out) +{ + int error; + git_buf path = GIT_BUF_INIT; + + if ((error = git_config_find_global_r(&path)) < 0) + return error; + + error = git_config_open_ondisk(out, git_buf_cstr(&path)); + git_buf_free(&path); + + return error; } diff --git a/src/config.h b/src/config.h index 8b521543c..82e98ce51 100644 --- a/src/config.h +++ b/src/config.h @@ -1,14 +1,33 @@ +/* + * 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_config_h__ #define INCLUDE_config_h__ #include "git2.h" #include "git2/config.h" #include "vector.h" +#include "repository.h" #define GIT_CONFIG_FILENAME ".gitconfig" +#define GIT_CONFIG_FILENAME_INREPO "config" +#define GIT_CONFIG_FILENAME_SYSTEM "gitconfig" +#define GIT_CONFIG_FILE_MODE 0666 struct git_config { + git_refcount rc; git_vector files; }; +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 new file mode 100644 index 000000000..ca9602e56 --- /dev/null +++ b/src/config_cache.c @@ -0,0 +1,94 @@ +/* + * 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 "fileops.h" +#include "config.h" +#include "git2/config.h" +#include "vector.h" +#include "filter.h" +#include "repository.h" + +struct map_data { + const char *cvar_name; + git_cvar_map *maps; + size_t map_count; + int default_value; +}; + +/* + * core.eol + * Sets the line ending type to use in the working directory for + * files that have the text property set. Alternatives are lf, crlf + * and native, which uses the platform’s native line ending. The default + * value is native. See gitattributes(5) for more information on + * end-of-line conversion. + */ +static git_cvar_map _cvar_map_eol[] = { + {GIT_CVAR_FALSE, NULL, GIT_EOL_UNSET}, + {GIT_CVAR_STRING, "lf", GIT_EOL_LF}, + {GIT_CVAR_STRING, "crlf", GIT_EOL_CRLF}, + {GIT_CVAR_STRING, "native", GIT_EOL_NATIVE} +}; + +/* + * core.autocrlf + * Setting this variable to "true" is almost the same as setting + * the text attribute to "auto" on all files except that text files are + * not guaranteed to be normalized: files that contain CRLF in the + * repository will not be touched. Use this setting if you want to have + * CRLF line endings in your working directory even though the repository + * does not have normalized line endings. This variable can be set to input, + * in which case no output conversion is performed. + */ +static git_cvar_map _cvar_map_autocrlf[] = { + {GIT_CVAR_FALSE, NULL, GIT_AUTO_CRLF_FALSE}, + {GIT_CVAR_TRUE, NULL, GIT_AUTO_CRLF_TRUE}, + {GIT_CVAR_STRING, "input", GIT_AUTO_CRLF_INPUT} +}; + +static struct map_data _cvar_maps[] = { + {"core.autocrlf", _cvar_map_autocrlf, ARRAY_SIZE(_cvar_map_autocrlf), GIT_AUTO_CRLF_DEFAULT}, + {"core.eol", _cvar_map_eol, ARRAY_SIZE(_cvar_map_eol), GIT_EOL_DEFAULT} +}; + +int git_repository__cvar(int *out, git_repository *repo, git_cvar_cached cvar) +{ + *out = repo->cvar_cache[(int)cvar]; + + if (*out == GIT_CVAR_NOT_CACHED) { + struct map_data *data = &_cvar_maps[(int)cvar]; + git_config *config; + int error; + + error = git_repository_config__weakptr(&config, repo); + if (error < 0) + return error; + + error = git_config_get_mapped(out, + config, data->cvar_name, data->maps, data->map_count); + + if (error == GIT_ENOTFOUND) + *out = data->default_value; + + else if (error < 0) + return error; + + repo->cvar_cache[(int)cvar] = *out; + } + + return 0; +} + +void git_repository__cvar_cache_clear(git_repository *repo) +{ + int i; + + for (i = 0; i < GIT_CVAR_CACHE_MAX; ++i) + repo->cvar_cache[i] = GIT_CVAR_NOT_CACHED; +} + diff --git a/src/config_file.c b/src/config_file.c index d76c6024d..1c748fad1 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -1,40 +1,28 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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 "config.h" #include "fileops.h" +#include "filebuf.h" +#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 *section; - char *name; + char *key; /* TODO: we might be able to get rid of this */ char *value; } cvar_t; @@ -84,10 +72,10 @@ typedef struct { typedef struct { git_config_file parent; - cvar_t_list var_list; + git_strmap *values; struct { - gitfo_buf buffer; + git_buf buffer; char *read_ptr; int line_number; int eof; @@ -98,290 +86,381 @@ typedef struct { 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) return; - free(var->section); - free(var->name); - free(var->value); - free(var); + git__free(var->key); + git__free(var->value); + git__free(var); } -static void cvar_list_free(cvar_t_list *list) +/* Take something the user gave us and make it nice for our hash function */ +static int normalize_name(const char *in, char **out) { - cvar_t *cur; + char *name, *fdot, *ldot; + + assert(in && out); + + name = git__strdup(in); + GITERR_CHECK_ALLOC(name); - while (!CVAR_LIST_EMPTY(list)) { - cur = CVAR_LIST_HEAD(list); - CVAR_LIST_REMOVE_HEAD(list); - cvar_free(cur); + fdot = strchr(name, '.'); + ldot = strrchr(name, '.'); + + if (fdot == NULL || ldot == NULL) { + git__free(name); + giterr_set(GITERR_CONFIG, + "Invalid variable name: '%s'", in); + return -1; } + + /* Downcase up to the first dot and after the last one */ + git__strntolower(name, fdot - name); + git__strtolower(ldot); + + *out = name; + return 0; } -/* - * Compare two strings according to the git section-subsection - * rules. The order of the strings is important because local is - * assumed to have the internal format (only the section name and with - * case information) and input the normalized one (only dots, no case - * information). - */ -static int cvar_match_section(const char *local, const char *input) +static void free_vars(git_strmap *values) { - char *first_dot, *last_dot; - char *local_sp = strchr(local, ' '); - int comparison_len; + cvar_t *var = NULL; - /* - * If the local section name doesn't contain a space, then we can - * just do a case-insensitive compare. - */ - if (local_sp == NULL) - return !strncasecmp(local, input, strlen(local)); + if (values == NULL) + return; - /* - * From here onwards, there is a space diving the section and the - * subsection. Anything before the space in local is - * case-insensitive. - */ - if (strncasecmp(local, input, local_sp - local)) - return 0; + git_strmap_foreach_value(values, var, + while (var != NULL) { + cvar_t *next = CVAR_LIST_NEXT(var); + cvar_free(var); + var = next; + }); - /* - * We compare starting from the first character after the - * quotation marks, which is two characters beyond the space. For - * the input, we start one character beyond the dot. If the names - * have different lengths, then we can fail early, as we know they - * can't be the same. - * The length is given by the length between the quotation marks. - */ + git_strmap_free(values); +} - first_dot = strchr(input, '.'); - last_dot = strrchr(input, '.'); - comparison_len = strlen(local_sp + 2) - 1; +static int config_open(git_config_file *cfg) +{ + int res; + diskfile_backend *b = (diskfile_backend *)cfg; + + b->values = git_strmap_alloc(); + GITERR_CHECK_ALLOC(b->values); - if (last_dot == first_dot || last_dot - first_dot - 1 != comparison_len) + git_buf_init(&b->reader.buffer, 0); + res = git_futils_readbuffer(&b->reader.buffer, b->file_path); + + /* It's fine if the file doesn't exist */ + if (res == GIT_ENOTFOUND) return 0; - return !strncmp(local_sp + 2, first_dot + 1, comparison_len); + if (res < 0 || config_parse(b) < 0) { + free_vars(b->values); + b->values = NULL; + git_buf_free(&b->reader.buffer); + return -1; + } + + git_buf_free(&b->reader.buffer); + return 0; } -static int cvar_match_name(const cvar_t *var, const char *str) +static void backend_free(git_config_file *_backend) { - const char *name_start; + diskfile_backend *backend = (diskfile_backend *)_backend; - if (!cvar_match_section(var->section, str)) { - return 0; - } - /* Early exit if the lengths are different */ - name_start = strrchr(str, '.') + 1; - if (strlen(var->name) != strlen(name_start)) - return 0; + if (backend == NULL) + return; - return !strcasecmp(var->name, name_start); + git__free(backend->file_path); + free_vars(backend->values); + git__free(backend); } -static cvar_t *cvar_list_find(cvar_t_list *list, const char *name) +static int file_foreach(git_config_file *backend, int (*fn)(const char *, const char *, void *), void *data) { - cvar_t *iter; + diskfile_backend *b = (diskfile_backend *)backend; + cvar_t *var; + const char *key; - CVAR_LIST_FOREACH (list, iter) { - if (cvar_match_name(iter, name)) - return iter; - } + if (!b->values) + return 0; - return NULL; + git_strmap_foreach(b->values, key, var, + do { + if (fn(key, var->value, data) < 0) + break; + + var = CVAR_LIST_NEXT(var); + } while (var != NULL); + ); + + return 0; } -static int cvar_normalize_name(cvar_t *var, char **output) +static int config_set(git_config_file *cfg, const char *name, const char *value) { - char *section_sp = strchr(var->section, ' '); - char *quote, *name; - int len, ret; + cvar_t *var = NULL, *old_var; + diskfile_backend *b = (diskfile_backend *)cfg; + char *key; + khiter_t pos; + int rval; + + if (normalize_name(name, &key) < 0) + return -1; /* - * The final string is going to be at most one char longer than - * the input + * Try to find it in the existing values and update it if it + * only has one value. */ - len = strlen(var->section) + strlen(var->name) + 1; - name = git__malloc(len + 1); - if (name == NULL) - return GIT_ENOMEM; + 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) { + giterr_set(GITERR_CONFIG, "Multivar incompatible with simple set"); + return -1; + } - /* If there aren't any spaces in the section, it's easy */ - if (section_sp == NULL) { - ret = snprintf(name, len + 1, "%s.%s", var->section, var->name); - if (ret < 0) - return git__throw(GIT_EOSERR, "Failed to normalize name. OS err: %s", strerror(errno)); + if (value) { + tmp = git__strdup(value); + GITERR_CHECK_ALLOC(tmp); + } - *output = name; - return GIT_SUCCESS; - } + git__free(existing->value); + existing->value = tmp; - /* - * If there are spaces, we replace the space by a dot, move - * section name so it overwrites the first quotation mark and - * replace the last quotation mark by a dot. We then append the - * variable name. - */ - strcpy(name, var->section); - section_sp = strchr(name, ' '); - *section_sp = '.'; - /* Remove first quote */ - quote = strchr(name, '"'); - memmove(quote, quote+1, strlen(quote+1)); - /* Remove second quote */ - quote = strchr(name, '"'); - *quote = '.'; - strcpy(quote+1, var->name); - - *output = name; - return GIT_SUCCESS; -} + return config_write(b, existing->key, NULL, value); + } -static int config_open(git_config_file *cfg) -{ - int error; - diskfile_backend *b = (diskfile_backend *)cfg; + var = git__malloc(sizeof(cvar_t)); + GITERR_CHECK_ALLOC(var); - error = gitfo_read_file(&b->reader.buffer, b->file_path); - if(error < GIT_SUCCESS) - goto cleanup; + memset(var, 0x0, sizeof(cvar_t)); - error = config_parse(b); - if (error < GIT_SUCCESS) - goto cleanup; + var->key = key; + var->value = NULL; - gitfo_free_buf(&b->reader.buffer); + if (value) { + var->value = git__strdup(value); + GITERR_CHECK_ALLOC(var->value); + } - return error; + if (config_write(b, key, NULL, value) < 0) { + cvar_free(var); + return -1; + } - cleanup: - cvar_list_free(&b->var_list); - gitfo_free_buf(&b->reader.buffer); + git_strmap_insert2(b->values, key, var, old_var, rval); + if (rval < 0) + return -1; + if (old_var != NULL) + cvar_free(old_var); - return git__rethrow(error, "Failed to open config"); + return 0; } -static void backend_free(git_config_file *_backend) +/* + * Internal function that actually gets the value in string form + */ +static int config_get(git_config_file *cfg, const char *name, const char **out) { - diskfile_backend *backend = (diskfile_backend *)_backend; + diskfile_backend *b = (diskfile_backend *)cfg; + char *key; + khiter_t pos; - if (backend == NULL) - return; + if (normalize_name(name, &key) < 0) + return -1; - free(backend->file_path); - cvar_list_free(&backend->var_list); + pos = git_strmap_lookup_index(b->values, key); + git__free(key); - free(backend); + /* no error message; the config system will write one */ + if (!git_strmap_valid_index(b->values, pos)) + return GIT_ENOTFOUND; + + *out = ((cvar_t *)git_strmap_value_at(b->values, pos))->value; + + return 0; } -static int file_foreach(git_config_file *backend, 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) { - int ret = GIT_SUCCESS; cvar_t *var; - diskfile_backend *b = (diskfile_backend *)backend; + diskfile_backend *b = (diskfile_backend *)cfg; + char *key; + khiter_t pos; - CVAR_LIST_FOREACH(&b->var_list, var) { - char *normalized = NULL; + if (normalize_name(name, &key) < 0) + return -1; - ret = cvar_normalize_name(var, &normalized); - if (ret < GIT_SUCCESS) - return ret; + pos = git_strmap_lookup_index(b->values, key); + git__free(key); - ret = fn(normalized, data); - free(normalized); - if (ret) - break; + if (!git_strmap_valid_index(b->values, pos)) + return GIT_ENOTFOUND; + + var = git_strmap_value_at(b->values, pos); + + 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; + } + + /* 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); } - return ret; + return 0; } -static int config_set(git_config_file *cfg, const char *name, const char *value) +static int config_set_multivar( + git_config_file *cfg, const char *name, const char *regexp, const char *value) { - cvar_t *var = NULL; - cvar_t *existing = NULL; - int error = GIT_SUCCESS; - const char *last_dot; + int replaced = 0; + cvar_t *var, *newvar; diskfile_backend *b = (diskfile_backend *)cfg; + char *key; + regex_t preg; + int result; + khiter_t pos; - /* - * If it already exists, we just need to update its value. - */ - existing = cvar_list_find(&b->var_list, name); - if (existing != NULL) { - char *tmp = value ? git__strdup(value) : NULL; - if (tmp == NULL && value != NULL) - return GIT_ENOMEM; + assert(regexp); - free(existing->value); - existing->value = tmp; + if (normalize_name(name, &key) < 0) + return -1; - return GIT_SUCCESS; + pos = git_strmap_lookup_index(b->values, key); + if (!git_strmap_valid_index(b->values, pos)) { + git__free(key); + return GIT_ENOTFOUND; } - /* - * Otherwise, create it and stick it at the end of the queue. - */ + var = git_strmap_value_at(b->values, pos); - last_dot = strrchr(name, '.'); - if (last_dot == NULL) { - return git__throw(GIT_EINVALIDTYPE, "Variables without section aren't allowed"); + result = regcomp(&preg, regexp, REG_EXTENDED); + if (result < 0) { + git__free(key); + giterr_set_regex(&preg, result); + return -1; } - var = git__malloc(sizeof(cvar_t)); - if (var == NULL) - return GIT_ENOMEM; + for (;;) { + if (regexec(&preg, var->value, 0, NULL, 0) == 0) { + char *tmp = git__strdup(value); + GITERR_CHECK_ALLOC(tmp); - memset(var, 0x0, sizeof(cvar_t)); + git__free(var->value); + var->value = tmp; + replaced = 1; + } - var->section = git__strndup(name, last_dot - name); - if (var->section == NULL) { - error = GIT_ENOMEM; - goto out; - } + if (var->next == NULL) + break; - var->name = git__strdup(last_dot + 1); - if (var->name == NULL) { - error = GIT_ENOMEM; - goto out; + var = var->next; } - var->value = value ? git__strdup(value) : NULL; - if (var->value == NULL && value != NULL) { - error = GIT_ENOMEM; - goto out; + /* 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)); + GITERR_CHECK_ALLOC(newvar); + + memset(newvar, 0x0, sizeof(cvar_t)); + + newvar->key = git__strdup(var->key); + GITERR_CHECK_ALLOC(newvar->key); + + newvar->value = git__strdup(value); + GITERR_CHECK_ALLOC(newvar->value); + + var->next = newvar; } - CVAR_LIST_APPEND(&b->var_list, var); + result = config_write(b, key, &preg, value); - out: - if (error < GIT_SUCCESS) - cvar_free(var); + git__free(key); + regfree(&preg); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to set config value"); + return result; } -/* - * Internal function that actually gets the value in string form - */ -static int config_get(git_config_file *cfg, const char *name, const char **out) +static int config_delete(git_config_file *cfg, const char *name) { cvar_t *var; - int error = GIT_SUCCESS; diskfile_backend *b = (diskfile_backend *)cfg; + char *key; + int result; + khiter_t pos; - var = cvar_list_find(&b->var_list, name); + if (normalize_name(name, &key) < 0) + return -1; - if (var == NULL) - return git__throw(GIT_ENOTFOUND, "Variable '%s' not found", name); + pos = git_strmap_lookup_index(b->values, key); + git__free(key); + + if (!git_strmap_valid_index(b->values, pos)) { + giterr_set(GITERR_CONFIG, "Could not find key '%s' to delete", name); + return GIT_ENOTFOUND; + } + + 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; + } - *out = var->value; + git_strmap_delete_at(b->values, pos); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to get config value for %s", name); + result = config_write(b, var->key, NULL, NULL); + + cvar_free(var); + return result; } int git_config_file__ondisk(git_config_file **out, const char *path) @@ -389,26 +468,25 @@ 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) { - free(backend); - return GIT_ENOMEM; - } + GITERR_CHECK_ALLOC(backend->file_path); backend->parent.open = config_open; backend->parent.get = config_get; + backend->parent.get_multivar = config_get_multivar; backend->parent.set = config_set; + backend->parent.set_multivar = config_set_multivar; + backend->parent.del = config_delete; backend->parent.foreach = file_foreach; backend->parent.free = backend_free; *out = (git_config_file *)backend; - return GIT_SUCCESS; + return 0; } static int cfg_getchar_raw(diskfile_backend *cfg) @@ -449,7 +527,8 @@ static int cfg_getchar(diskfile_backend *cfg_file, int flags) assert(cfg_file->reader.read_ptr); do c = cfg_getchar_raw(cfg_file); - while (skip_whitespace && isspace(c)); + while (skip_whitespace && git__isspace(c) && + !cfg_file->reader.eof); if (skip_comments && (c == '#' || c == ';')) { do c = cfg_getchar_raw(cfg_file); @@ -486,21 +565,23 @@ 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; - int line_len; + size_t line_len; line_src = cfg->reader.read_ptr; - /* Skip empty empty lines */ - while (isspace(*line_src)) - ++line_src; + if (skip_whitespace) { + /* Skip empty empty lines */ + while (git__isspace(*line_src)) + ++line_src; + } - line_end = strchr(line_src, '\n'); + line_end = strchr(line_src, '\n'); - /* no newline at EOF */ + /* no newline at EOF */ if (line_end == NULL) line_end = strchr(line_src, 0); @@ -512,10 +593,8 @@ static char *cfg_readline(diskfile_backend *cfg) memcpy(line, line_src, line_len); - line[line_len] = '\0'; - - while (--line_len >= 0 && isspace(line[line_len])) - line[line_len] = '\0'; + do line[line_len] = '\0'; + while (line_len-- > 0 && git__isspace(line[line_len])); if (*line_end == '\n') line_end++; @@ -532,7 +611,7 @@ static char *cfg_readline(diskfile_backend *cfg) /* * Consume a line, without storing it anywhere */ -void cfg_consume_line(diskfile_backend *cfg) +static void cfg_consume_line(diskfile_backend *cfg) { char *line_start, *line_end; @@ -558,12 +637,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 buf_len, total_len, pos, rpos; - int c, ret; - char *subsection, *first_quote, *last_quote; - int error = GIT_SUCCESS; + int c, rpos; + char *first_quote, *last_quote; + git_buf buf = GIT_BUF_INIT; int quote_marks; /* * base_name is what came before the space. We should be at the @@ -574,16 +652,14 @@ 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"); - - buf_len = last_quote - first_quote + 2; + if (last_quote - first_quote == 0) { + set_parse_error(cfg, 0, "Missing closing quotation mark in section header"); + return -1; + } - subsection = git__malloc(buf_len + 2); - if (subsection == NULL) - return GIT_ENOMEM; + git_buf_grow(&buf, strlen(base_name) + last_quote - first_quote + 2); + git_buf_printf(&buf, "%s.", base_name); - pos = 0; rpos = 0; quote_marks = 0; @@ -596,139 +672,123 @@ static int parse_section_header_ext(const char *line, const char *base_name, cha */ do { if (quote_marks == 2) { - 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; - break; + 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; } - subsection[pos++] = (char) c; + git_buf_putc(&buf, c); } while ((c = line[rpos++]) != ']'); - subsection[pos] = '\0'; - - total_len = strlen(base_name) + strlen(subsection) + 2; - *section_name = git__malloc(total_len); - if (*section_name == NULL) { - error = GIT_ENOMEM; - goto out; - } - - ret = snprintf(*section_name, total_len, "%s %s", base_name, subsection); - if (ret >= total_len) { - /* If this fails, we've checked the length wrong */ - error = git__throw(GIT_ERROR, "Failed to parse ext header. Wrong total length calculation"); - goto out; - } else if (ret < 0) { - error = git__throw(GIT_EOSERR, "Failed to parse ext header. OS error: %s", strerror(errno)); - goto out; - } - - git__strntolower(*section_name, strchr(*section_name, ' ') - *section_name); - - out: - free(subsection); - - 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) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse header. Can't find header name end"); + if (name_end == NULL) { + git__free(line); + set_parse_error(cfg, 0, "Missing ']' in section header"); + return -1; + } name = (char *)git__malloc((size_t)(name_end - line) + 1); - if (name == NULL) - 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 (cfg->reader.eof){ - error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse header. Config file ended unexpectedly"); - goto error; - } - - if (isspace(c)){ + if (git__isspace(c)){ name[name_length] = '\0'; - error = parse_section_header_ext(line, name, section_out); - free(line); - free(name); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse header"); + result = parse_section_header_ext(cfg, line, name, section_out); + git__free(line); + git__free(name); + 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); } while ((c = line[pos++]) != ']'); + if (line[pos - 1] != ']') { + set_parse_error(cfg, pos, "Unexpected end of file"); + goto fail_parse; + } + + git__free(line); + name[name_length] = 0; - free(line); - git__strtolower(name); *section_out = name; - return GIT_SUCCESS; -error: - free(line); - free(name); - return error; + return 0; + +fail_parse: + git__free(line); + git__free(name); + return -1; } static int skip_bom(diskfile_backend *cfg) { - static const char *utf8_bom = "\xef\xbb\xbf"; + static const char utf8_bom[] = "\xef\xbb\xbf"; + + if (cfg->reader.buffer.size < sizeof(utf8_bom)) + return 0; if (memcmp(cfg->reader.read_ptr, utf8_bom, sizeof(utf8_bom)) == 0) cfg->reader.read_ptr += sizeof(utf8_bom); - /* TODO: the reference implementation does pretty stupid + /* TODO: the reference implementation does pretty stupid shit with the BoM */ - return GIT_SUCCESS; + return 0; } /* @@ -770,9 +830,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) { @@ -785,41 +845,49 @@ static void strip_comments(char *line) } } - if (isspace(ptr[-1])) { - /* TODO skip whitespace */ + /* skip any space at the end */ + if (git__isspace(ptr[-1])) { + 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; + 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.data; + cfg_file->reader.read_ptr = cfg_file->reader.buffer.ptr; cfg_file->reader.eof = 0; /* 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); switch (c) { - case '\0': /* We've arrived at the end of the file */ + case '\n': /* EOF when peeking, set EOF in the reader to exit the loop */ + cfg_file->reader.eof = 1; break; case '[': /* section header, new section begins */ - free(current_section); + 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 ';': @@ -828,129 +896,376 @@ 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 = malloc(sizeof(cvar_t)); - if (var == NULL) { - error = GIT_ENOMEM; - break; - } + var = git__malloc(sizeof(cvar_t)); + GITERR_CHECK_ALLOC(var); memset(var, 0x0, sizeof(cvar_t)); - var->section = git__strdup(current_section); - if (var->section == NULL) { - error = GIT_ENOMEM; - free(var); - break; - } + git__strtolower(var_name); + git_buf_printf(&buf, "%s.%s", current_section, var_name); + git__free(var_name); + + if (git_buf_oom(&buf)) + return -1; - var->name = var_name; + var->key = git_buf_detach(&buf); var->value = var_value; - git__strtolower(var->name); - CVAR_LIST_APPEND(&cfg_file->var_list, var); + /* Add or append the new config option */ + 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; + } + existing->next = var; + } break; } } - free(current_section); + git__free(current_section); + return result; +} - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse config"); +static int write_section(git_filebuf *file, const char *key) +{ + int result; + const char *dot; + git_buf buf = GIT_BUF_INIT; + + /* All of this just for [section "subsection"] */ + dot = strchr(key, '.'); + git_buf_putc(&buf, '['); + if (dot == NULL) { + git_buf_puts(&buf, key); + } else { + git_buf_put(&buf, key, dot - key); + /* TODO: escape */ + git_buf_printf(&buf, " \"%s\"", dot + 1); + } + git_buf_puts(&buf, "]\n"); + + if (git_buf_oom(&buf)) + return -1; + + result = git_filebuf_write(file, git_buf_cstr(&buf), buf.size); + git_buf_free(&buf); + + return result; } -static int is_multiline_var(const char *str) +/* + * This is pretty much the parsing, except we write out anything we don't have + */ +static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char* value) +{ + 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; + git_filebuf file = GIT_FILEBUF_INIT; + + /* We need to read in our own config file */ + result = git_futils_readbuffer(&cfg->reader.buffer, cfg->file_path); + + /* Initialise the reading position */ + if (result == GIT_ENOTFOUND) { + cfg->reader.read_ptr = NULL; + cfg->reader.eof = 1; + data_start = NULL; + git_buf_clear(&cfg->reader.buffer); + } 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 */ + 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 (!cfg->reader.eof) { + c = cfg_peek(cfg, SKIP_WHITESPACE); + + if (c == '\0') { /* We've arrived at the end of the file */ + break; + + } 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 + * case, we want both variables to point just before the + * new section. If we actually want to replace it, the + * default case will take care of updating them. + */ + pre_end = post_start = cfg->reader.read_ptr; + + git__free(current_section); + current_section = NULL; + 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); + } + + else if (c == ';' || c == '#') { + cfg_consume_line(cfg); + } + + else { + /* + * If the section doesn't match, but the last section did, + * it means we need to add a variable (so skip the line + * otherwise). If both the section and name match, we need + * to overwrite the variable (so skip the line + * otherwise). pre_end needs to be updated each time so we + * don't loose that information, but we only need to + * update post_start if we're going to use it in this + * iteration. + */ + if (!section_matches) { + if (!last_section_matched) { + cfg_consume_line(cfg); + continue; + } + } else { + int has_matched = 0; + char *var_name, *var_value; + + pre_end = cfg->reader.read_ptr; + 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 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 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 */ + git_filebuf_write(&file, data_start, pre_end - data_start); + preg_replaced = 1; + + /* 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; + } + + write_trailer = 1; + break; /* break from the loop */ + } + } + + /* + * Being here can mean that + * + * 1) our section is the last one in the file and we're + * adding a variable + * + * 2) we didn't find a section for us so we need to create it + * ourselves. + * + * 3) we're setting a multivar with a regex, which means we + * continue to search for matching values + * + * In the last case, if we've already replaced a value, we + * want to write the rest of the file. Otherwise we need to write + * out the whole file and then the new variable. + */ + 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; + } + + git_filebuf_printf(&file, "\t%s = %s\n", name, value); + } + } + + git__free(section); + git__free(current_section); + + result = git_filebuf_commit(&file, GIT_CONFIG_FILE_MODE); + git_buf_free(&cfg->reader.buffer); + return result; + +rewrite_fail: + git__free(section); + git__free(current_section); + + git_filebuf_cleanup(&file); + git_buf_free(&cfg->reader.buffer); + return -1; +} + +/* '\"' -> '"' 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\"\\"; - while (isspace(*end)) - --end; + if (str == NULL) + return NULL; - return *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; +} + +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, len, ret; - 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; + /* 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); - *end = '\0'; /* Terminate the string here */ - - len = strlen(first) + strlen(line) + 2; - buf = git__malloc(len); - if (buf == NULL) { - error = GIT_ENOMEM; - goto out; - } - - ret = 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)); - 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); - free(buf); - buf = final_val; - } - - *out = buf; + if (is_multiline_var(value->ptr)) + return parse_multiline_variable(cfg, value, quote_count); - out: - 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, '='); @@ -959,51 +1274,47 @@ static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_val else value_start = var_end + 1; - if (isspace(var_end[-1])) { + if (git__isspace(var_end[-1])) { do var_end--; - while (isspace(var_end[0])); + while (git__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])) + while (git__isspace(value_start[0])) value_start++; - if (value_start[0] == '\0') - goto out; - if (is_multiline_var(value_start)) { - error = parse_multiline_variable(cfg, value_start, var_value); - if (error < GIT_SUCCESS) - free(*var_name); - goto out; - } + 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; + } - tmp = strdup(value_start); - if (tmp == NULL) { - free(*var_name); - 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: - free(line); - return error; + git__free(line); + 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 new file mode 100644 index 000000000..303a46d3b --- /dev/null +++ b/src/crlf.c @@ -0,0 +1,228 @@ +/* + * 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 "fileops.h" +#include "hash.h" +#include "filter.h" +#include "repository.h" + +#include "git2/attr.h" + +struct crlf_attrs { + int crlf_action; + int eol; +}; + +struct crlf_filter { + git_filter f; + struct crlf_attrs attrs; +}; + +static int check_crlf(const char *value) +{ + if (GIT_ATTR_TRUE(value)) + return GIT_CRLF_TEXT; + + if (GIT_ATTR_FALSE(value)) + return GIT_CRLF_BINARY; + + if (GIT_ATTR_UNSPECIFIED(value)) + return GIT_CRLF_GUESS; + + if (strcmp(value, "input") == 0) + return GIT_CRLF_INPUT; + + if (strcmp(value, "auto") == 0) + return GIT_CRLF_AUTO; + + return GIT_CRLF_GUESS; +} + +static int check_eol(const char *value) +{ + if (GIT_ATTR_UNSPECIFIED(value)) + return GIT_EOL_UNSET; + + if (strcmp(value, "lf") == 0) + return GIT_EOL_LF; + + if (strcmp(value, "crlf") == 0) + return GIT_EOL_CRLF; + + return GIT_EOL_UNSET; +} + +static int crlf_input_action(struct crlf_attrs *ca) +{ + if (ca->crlf_action == GIT_CRLF_BINARY) + return GIT_CRLF_BINARY; + + if (ca->eol == GIT_EOL_LF) + return GIT_CRLF_INPUT; + + if (ca->eol == GIT_EOL_CRLF) + return GIT_CRLF_CRLF; + + return ca->crlf_action; +} + +static int crlf_load_attributes(struct crlf_attrs *ca, git_repository *repo, const char *path) +{ +#define NUM_CONV_ATTRS 3 + + static const char *attr_names[NUM_CONV_ATTRS] = { + "crlf", "eol", "text", + }; + + const char *attr_vals[NUM_CONV_ATTRS]; + int error; + + error = git_attr_get_many(attr_vals, + repo, 0, path, NUM_CONV_ATTRS, attr_names); + + if (error == GIT_ENOTFOUND) { + ca->crlf_action = GIT_CRLF_GUESS; + ca->eol = GIT_EOL_UNSET; + return 0; + } + + if (error == 0) { + ca->crlf_action = check_crlf(attr_vals[2]); /* text */ + if (ca->crlf_action == GIT_CRLF_GUESS) + ca->crlf_action = check_crlf(attr_vals[0]); /* clrf */ + + ca->eol = check_eol(attr_vals[1]); /* eol */ + return 0; + } + + return -1; +} + +static int drop_crlf(git_buf *dest, const git_buf *source) +{ + const char *scan = source->ptr, *next; + 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. + */ + while ((next = memchr(scan, '\r', scan_end - scan)) != NULL) { + /* copy input up to \r */ + if (next > scan) + git_buf_put(dest, scan, next - scan); + + /* Do not drop \r unless it is followed by \n */ + if (*(next + 1) != '\n') + git_buf_putc(dest, '\r'); + + scan = next + 1; + } + + /* If there was no \r, then tell the library to skip this filter */ + if (scan == source->ptr) + return -1; + + /* Copy remaining input into dest */ + git_buf_put(dest, scan, scan_end - scan); + return 0; +} + +static int crlf_apply_to_odb(git_filter *self, git_buf *dest, const git_buf *source) +{ + struct crlf_filter *filter = (struct crlf_filter *)self; + + assert(self && dest && source); + + /* Empty file? Nothing to do */ + if (git_buf_len(source) == 0) + return 0; + + /* Heuristics to see if we can skip the conversion. + * Straight from Core Git. + */ + if (filter->attrs.crlf_action == GIT_CRLF_AUTO || + filter->attrs.crlf_action == GIT_CRLF_GUESS) { + + git_text_stats stats; + git_text_gather_stats(&stats, source); + + /* + * We're currently not going to even try to convert stuff + * that has bare CR characters. Does anybody do that crazy + * stuff? + */ + if (stats.cr != stats.crlf) + return -1; + + /* + * And add some heuristics for binary vs text, of course... + */ + if (git_text_is_binary(&stats)) + return -1; + +#if 0 + if (crlf_action == CRLF_GUESS) { + /* + * If the file in the index has any CR in it, do not convert. + * This is the new safer autocrlf handling. + */ + if (has_cr_in_index(path)) + return 0; + } +#endif + + if (!stats.cr) + return -1; + } + + /* Actually drop the carriage returns */ + return drop_crlf(dest, source); +} + +int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const char *path) +{ + struct crlf_attrs ca; + struct crlf_filter *filter; + int error; + + /* Load gitattributes for the path */ + if ((error = crlf_load_attributes(&ca, repo, path)) < 0) + return error; + + /* + * Use the core Git logic to see if we should perform CRLF for this file + * based on its attributes & the value of `core.auto_crlf` + */ + ca.crlf_action = crlf_input_action(&ca); + + if (ca.crlf_action == GIT_CRLF_BINARY) + return 0; + + if (ca.crlf_action == GIT_CRLF_GUESS) { + int auto_crlf; + + if ((error = git_repository__cvar( + &auto_crlf, repo, GIT_CVAR_AUTO_CRLF)) < 0) + return error; + + if (auto_crlf == GIT_AUTO_CRLF_FALSE) + return 0; + } + + /* If we're good, we create a new filter object and push it + * into the filters array */ + filter = git__malloc(sizeof(struct crlf_filter)); + GITERR_CHECK_ALLOC(filter); + + filter->f.apply = &crlf_apply_to_odb; + filter->f.do_free = NULL; + memcpy(&filter->attrs, &ca, sizeof(struct crlf_attrs)); + + return git_vector_insert(filters, filter); +} + diff --git a/src/delta-apply.c b/src/delta-apply.c index a6b711436..815ca8f16 100644 --- a/src/delta-apply.c +++ b/src/delta-apply.c @@ -1,3 +1,9 @@ +/* + * 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 "git2/odb.h" #include "delta-apply.h" @@ -5,7 +11,7 @@ /* * This file was heavily cribbed from BinaryDelta.java in JGit, which * itself was heavily cribbed from <code>patch-delta.c</code> in the - * GIT project. The original delta patching code was written by + * GIT project. The original delta patching code was written by * Nicolas Pitre <nico@cam.org>. */ @@ -45,14 +51,19 @@ 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; + } + + res_dp = git__malloc(res_sz + 1); + GITERR_CHECK_ALLOC(res_dp); - if ((res_dp = git__malloc(res_sz + 1)) == NULL) - return GIT_ENOMEM; res_dp[res_sz] = '\0'; out->data = res_dp; out->len = res_sz; @@ -64,15 +75,15 @@ int git__delta_apply( */ size_t off = 0, len = 0; - if (cmd & 0x01) off = *delta++; - if (cmd & 0x02) off |= *delta++ << 8; + if (cmd & 0x01) off = *delta++; + if (cmd & 0x02) off |= *delta++ << 8; if (cmd & 0x04) off |= *delta++ << 16; if (cmd & 0x08) off |= *delta++ << 24; - if (cmd & 0x10) len = *delta++; - if (cmd & 0x20) len |= *delta++ << 8; + if (cmd & 0x10) len = *delta++; + if (cmd & 0x20) len |= *delta++ << 8; if (cmd & 0x40) len |= *delta++ << 16; - if (!len) len = 0x10000; + if (!len) len = 0x10000; if (base_len < off + len || res_sz < len) goto fail; @@ -87,7 +98,7 @@ int git__delta_apply( if (delta_end - delta < cmd || res_sz < cmd) goto fail; memcpy(res_dp, delta, cmd); - delta += cmd; + delta += cmd; res_dp += cmd; res_sz -= cmd; @@ -100,10 +111,11 @@ int git__delta_apply( if (delta != delta_end || res_sz) goto fail; - return GIT_SUCCESS; + return 0; fail: - free(out->data); + 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/delta-apply.h b/src/delta-apply.h index 36c5cc60d..66fa76d43 100644 --- a/src/delta-apply.h +++ b/src/delta-apply.h @@ -1,3 +1,9 @@ +/* + * 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_delta_apply_h__ #define INCLUDE_delta_apply_h__ @@ -14,7 +20,7 @@ * @param delta the delta to execute copy/insert instructions from. * @param delta_len total number of bytes in the delta. * @return - * - GIT_SUCCESS on a successful delta unpack. + * - 0 on a successful delta unpack. * - GIT_ERROR if the delta is corrupt or doesn't match the base. */ extern int git__delta_apply( diff --git a/src/diff.c b/src/diff.c new file mode 100644 index 000000000..90baa9588 --- /dev/null +++ b/src/diff.c @@ -0,0 +1,784 @@ +/* + * 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/diff.h" +#include "diff.h" +#include "fileops.h" +#include "config.h" +#include "attr_file.h" + +static char *diff_prefix_from_pathspec(const git_strarray *pathspec) +{ + git_buf prefix = GIT_BUF_INIT; + const char *scan; + + if (git_buf_common_prefix(&prefix, pathspec) < 0) + return NULL; + + /* diff prefix will only be leading non-wildcards */ + for (scan = prefix.ptr; *scan && !git__iswildcard(*scan); ++scan); + git_buf_truncate(&prefix, scan - prefix.ptr); + + if (prefix.size > 0) + return git_buf_detach(&prefix); + + git_buf_free(&prefix); + return NULL; +} + +static bool diff_pathspec_is_interesting(const git_strarray *pathspec) +{ + const char *str; + + 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 = p_fnmatch(match->pattern, path, 0); + + /* if we didn't match, look for exact dirname prefix match */ + if (result == FNM_NOMATCH && + (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 && + strncmp(path, match->pattern, match->length) == 0 && + path[match->length] == '/') + result = 0; + + if (result == 0) + return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? false : true; + } + + return false; +} + +static git_diff_delta *diff_delta__alloc( + git_diff_list *diff, + git_delta_t status, + const char *path) +{ + git_diff_delta *delta = git__calloc(1, sizeof(git_diff_delta)); + if (!delta) + return NULL; + + delta->old_file.path = git_pool_strdup(&diff->pool, path); + if (delta->old_file.path == NULL) { + git__free(delta); + return NULL; + } + + delta->new_file.path = delta->old_file.path; + + if (diff->opts.flags & GIT_DIFF_REVERSE) { + switch (status) { + case GIT_DELTA_ADDED: status = GIT_DELTA_DELETED; break; + case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break; + default: break; /* leave other status values alone */ + } + } + delta->status = status; + + return delta; +} + +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) + return NULL; + + memcpy(delta, d, sizeof(git_diff_delta)); + + 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_pool_strdup(pool, d->new_file.path); + if (delta->new_file.path == NULL) + goto fail; + } else { + delta->new_file.path = delta->old_file.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, git_pool *pool) +{ + git_diff_delta *dup = diff_delta__dup(a, pool); + if (!dup) + return NULL; + + if (git_oid_cmp(&dup->new_file.oid, &b->new_file.oid) == 0) + return dup; + + git_oid_cpy(&dup->new_file.oid, &b->new_file.oid); + + dup->new_file.mode = b->new_file.mode; + dup->new_file.size = b->new_file.size; + dup->new_file.flags = b->new_file.flags; + + /* Emulate C git for merging two diffs (a la 'git diff <sha>'). + * + * When C git does a diff between the work dir and a tree, it actually + * diffs with the index but uses the workdir contents. This emulates + * those choices so we can emulate the type of diff. + */ + if (git_oid_cmp(&dup->old_file.oid, &dup->new_file.oid) == 0) { + if (dup->status == GIT_DELTA_DELETED) + /* preserve pending delete info */; + else if (b->status == GIT_DELTA_UNTRACKED || + b->status == GIT_DELTA_IGNORED) + dup->status = b->status; + else + dup->status = GIT_DELTA_UNMODIFIED; + } + else if (dup->status == GIT_DELTA_UNMODIFIED || + b->status == GIT_DELTA_DELETED) + dup->status = b->status; + + return dup; +} + +static int diff_delta__from_one( + git_diff_list *diff, + git_delta_t status, + const git_index_entry *entry) +{ + 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); + + if (delta->status == GIT_DELTA_DELETED) { + delta->old_file.mode = entry->mode; + delta->old_file.size = entry->file_size; + git_oid_cpy(&delta->old_file.oid, &entry->oid); + } else /* ADDED, IGNORED, UNTRACKED */ { + delta->new_file.mode = entry->mode; + delta->new_file.size = entry->file_size; + git_oid_cpy(&delta->new_file.oid, &entry->oid); + } + + delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID; + delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID; + + if (git_vector_insert(&diff->deltas, delta) < 0) { + git__free(delta); + return -1; + } + + return 0; +} + +static int diff_delta__from_two( + git_diff_list *diff, + git_delta_t status, + const git_index_entry *old_entry, + const git_index_entry *new_entry, + git_oid *new_oid) +{ + 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; + new_entry = temp; + } + + delta = diff_delta__alloc(diff, status, old_entry->path); + GITERR_CHECK_ALLOC(delta); + + delta->old_file.mode = old_entry->mode; + git_oid_cpy(&delta->old_file.oid, &old_entry->oid); + delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID; + + delta->new_file.mode = new_entry->mode; + git_oid_cpy(&delta->new_file.oid, new_oid ? new_oid : &new_entry->oid); + if (new_oid || !git_oid_iszero(&new_entry->oid)) + delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID; + + if (git_vector_insert(&diff->deltas, delta) < 0) { + git__free(delta); + return -1; + } + + return 0; +} + +static char *diff_strdup_prefix(git_pool *pool, const char *prefix) +{ + size_t len = strlen(prefix); + + /* 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) +{ + const git_diff_delta *da = a, *db = b; + int val = strcmp(da->old_file.path, db->old_file.path); + 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(&val, cfg, name) < 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->pool, + opts->old_prefix ? opts->old_prefix : DIFF_OLD_PREFIX_DEFAULT); + 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) + goto fail; + + if (diff->opts.flags & GIT_DIFF_REVERSE) { + char *swap = diff->opts.old_prefix; + diff->opts.old_prefix = diff->opts.new_prefix; + diff->opts.new_prefix = swap; + } + + /* 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; + + 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; + match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE; + 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) { + git__free(delta); + diff->deltas.contents[i] = NULL; + } + git_vector_free(&diff->deltas); + + 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); +} + +static int oid_for_workdir_item( + git_repository *repo, + const git_index_entry *item, + git_oid *oid) +{ + int result; + git_buf full_path = GIT_BUF_INIT; + + if (git_buf_joinpath(&full_path, git_repository_workdir(repo), item->path) < 0) + return -1; + + /* calculate OID for file if possible*/ + if (S_ISLNK(item->mode)) + 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 { + result = git_odb__hashfd( + oid, fd, (size_t)item->file_size, GIT_OBJ_BLOB); + p_close(fd); + } + } + + git_buf_free(&full_path); + + return result; +} + +#define EXEC_BIT_MASK 0000111 + +static int maybe_modified( + git_iterator *old_iter, + const git_index_entry *oitem, + git_iterator *new_iter, + const git_index_entry *nitem, + git_diff_list *diff) +{ + 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); + + 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); + + /* 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; + } + + /* 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 they files look exactly alike, then we'll assume the same */ + if (oitem->file_size == nitem->file_size && + (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) || + (oitem->ctime.seconds == nitem->ctime.seconds)) && + oitem->mtime.seconds == nitem->mtime.seconds && + (!(diff->diffcaps & GIT_DIFFCAPS_USE_DEV) || + (oitem->dev == nitem->dev)) && + oitem->ino == nitem->ino && + oitem->uid == nitem->uid && + oitem->gid == nitem->gid) + 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. + */ + + 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, status, oitem, nitem, use_noid); +} + +static int diff_from_iterators( + git_repository *repo, + const git_diff_options *opts, /**< can be NULL for defaults */ + git_iterator *old_iter, + git_iterator *new_iter, + git_diff_list **diff_ptr) +{ + const git_index_entry *oitem, *nitem; + git_buf ignore_prefix = GIT_BUF_INIT; + git_diff_list *diff = git_diff_list_alloc(repo, opts); + if (!diff) + goto fail; + + diff->old_src = old_iter->type; + diff->new_src = new_iter->type; + + if (git_iterator_current(old_iter, &oitem) < 0 || + git_iterator_current(new_iter, &nitem) < 0) + goto fail; + + /* run iterators building diffs */ + while (oitem || nitem) { + + /* create DELETED records for old items not matched in new */ + if (oitem && (!nitem || strcmp(oitem->path, nitem->path) < 0)) { + 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) + */ + else if (nitem && (!oitem || strcmp(oitem->path, nitem->path) > 0)) { + git_delta_t delta_type = GIT_DELTA_UNTRACKED; + + /* check if contained in ignored parent directory */ + if (git_buf_len(&ignore_prefix) && + git__prefixcmp(nitem->path, git_buf_cstr(&ignore_prefix)) == 0) + delta_type = GIT_DELTA_IGNORED; + + if (S_ISDIR(nitem->mode)) { + /* recurse into directory only if there are tracked items in + * it or if the user requested the contents of untracked + * directories and it is not under an ignored directory. + */ + if ((oitem && git__prefixcmp(oitem->path, nitem->path) == 0) || + (delta_type == GIT_DELTA_UNTRACKED && + (diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS) != 0)) + { + /* if this directory is ignored, remember it as the + * "ignore_prefix" for processing contained items + */ + if (delta_type == GIT_DELTA_UNTRACKED && + git_iterator_current_is_ignored(new_iter)) + git_buf_sets(&ignore_prefix, nitem->path); + + if (git_iterator_advance_into_directory(new_iter, &nitem) < 0) + goto fail; + + continue; + } + } + + /* In core git, the next two "else if" clauses are effectively + * reversed -- i.e. when an untracked file contained in an + * ignored directory is individually ignored, it shows up as an + * ignored file in the diff list, even though other untracked + * files in the same directory are skipped completely. + * + * To me, this is odd. If the directory is ignored and the file + * is untracked, we should skip it consistently, regardless of + * whether it happens to match a pattern in the ignore file. + * + * To match the core git behavior, just reverse the following + * two "else if" cases so that individual file ignores are + * checked before container directory exclusions are used to + * skip the file. + */ + else if (delta_type == GIT_DELTA_IGNORED) { + if (git_iterator_advance(new_iter, &nitem) < 0) + goto fail; + continue; /* ignored parent directory, so skip completely */ + } + + else if (git_iterator_current_is_ignored(new_iter)) + delta_type = GIT_DELTA_IGNORED; + + else if (new_iter->type != GIT_ITERATOR_WORKDIR) + delta_type = GIT_DELTA_ADDED; + + 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) + */ + else { + assert(oitem && nitem && strcmp(oitem->path, nitem->path) == 0); + + 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; + } + } + + git_iterator_free(old_iter); + git_iterator_free(new_iter); + git_buf_free(&ignore_prefix); + + *diff_ptr = diff; + return 0; + +fail: + git_iterator_free(old_iter); + git_iterator_free(new_iter); + git_buf_free(&ignore_prefix); + + git_diff_list_free(diff); + *diff_ptr = NULL; + return -1; +} + + +int git_diff_tree_to_tree( + git_repository *repo, + const git_diff_options *opts, /**< can be NULL for defaults */ + git_tree *old_tree, + git_tree *new_tree, + git_diff_list **diff) +{ + git_iterator *a = NULL, *b = NULL; + char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL; + + assert(repo && old_tree && new_tree && diff); + + if (git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix) < 0 || + git_iterator_for_tree_range(&b, repo, new_tree, prefix, prefix) < 0) + return -1; + + git__free(prefix); + + return diff_from_iterators(repo, opts, a, b, diff); +} + +int git_diff_index_to_tree( + git_repository *repo, + const git_diff_options *opts, + git_tree *old_tree, + git_diff_list **diff) +{ + git_iterator *a = NULL, *b = NULL; + char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL; + + assert(repo && diff); + + if (git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix) < 0 || + git_iterator_for_index_range(&b, repo, prefix, prefix) < 0) + return -1; + + git__free(prefix); + + return diff_from_iterators(repo, opts, a, b, diff); +} + +int git_diff_workdir_to_index( + git_repository *repo, + const git_diff_options *opts, + git_diff_list **diff) +{ + git_iterator *a = NULL, *b = NULL; + char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL; + + assert(repo && diff); + + if (git_iterator_for_index_range(&a, repo, prefix, prefix) < 0 || + git_iterator_for_workdir_range(&b, repo, prefix, prefix) < 0) + return -1; + + git__free(prefix); + + return diff_from_iterators(repo, opts, a, b, diff); +} + + +int git_diff_workdir_to_tree( + git_repository *repo, + const git_diff_options *opts, + git_tree *old_tree, + git_diff_list **diff) +{ + git_iterator *a = NULL, *b = NULL; + char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL; + + assert(repo && old_tree && diff); + + if (git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix) < 0 || + git_iterator_for_workdir_range(&b, repo, prefix, prefix) < 0) + return -1; + + git__free(prefix); + + return diff_from_iterators(repo, opts, a, b, diff); +} + +int git_diff_merge( + git_diff_list *onto, + const git_diff_list *from) +{ + int error = 0; + git_pool onto_pool; + git_vector onto_new; + git_diff_delta *delta; + unsigned int i, j; + + assert(onto && from); + + if (!from->deltas.length) + return 0; + + if (git_vector_init(&onto_new, onto->deltas.length, diff_delta__cmp) < 0 || + git_pool_init(&onto_pool, 1, 0) < 0) + return -1; + + 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 (cmp > 0) { + delta = diff_delta__dup(f, &onto_pool); + j++; + } else { + delta = diff_delta__merge_like_cgit(o, f, &onto_pool); + i++; + j++; + } + + if ((error = !delta ? -1 : git_vector_insert(&onto_new, delta)) < 0) + break; + } + + 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) + 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 new file mode 100644 index 000000000..ac2457956 --- /dev/null +++ b/src/diff.h @@ -0,0 +1,40 @@ +/* + * 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_diff_h__ +#define INCLUDE_diff_h__ + +#include <stdio.h> +#include "vector.h" +#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 new file mode 100644 index 000000000..5ffa641c4 --- /dev/null +++ b/src/diff_output.c @@ -0,0 +1,786 @@ +/* + * 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/diff.h" +#include "git2/attr.h" +#include "git2/blob.h" +#include "xdiff/xdiff.h" +#include <ctype.h> +#include "diff.h" +#include "map.h" +#include "fileops.h" +#include "filter.h" + +typedef struct { + git_diff_list *diff; + void *cb_data; + git_diff_hunk_fn hunk_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) +{ + const char *scan = *str; + int v = 0, digits = 0; + /* find next digit */ + for (scan = *str; *scan && !isdigit(*scan); scan++); + /* parse next number */ + for (; isdigit(*scan); scan++, digits++) + v = (v * 10) + (*scan - '0'); + *str = scan; + *value = v; + return (digits > 0) ? 0 : -1; +} + +static int diff_output_cb(void *priv, mmbuffer_t *bufs, int len) +{ + 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 (*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); + } + + if ((len == 2 || len == 3) && info->line_cb) { + int origin; + + /* expect " "/"-"/"+", then data, then maybe newline */ + origin = + (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_ADDITION : + (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION : + GIT_DIFF_LINE_CONTEXT; + + 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 (len == 3) { + if (origin == GIT_DIFF_LINE_ADDITION) + origin = GIT_DIFF_LINE_ADD_EOFNL; + else + origin = GIT_DIFF_LINE_DEL_EOFNL; + + return info->line_cb( + info->cb_data, info->delta, &info->range, origin, bufs[2].ptr, bufs[2].size); + } + } + + return 0; +} + +#define BINARY_DIFF_FLAGS (GIT_DIFF_FILE_BINARY|GIT_DIFF_FILE_NOT_BINARY) + +static int update_file_is_binary_by_attr(git_repository *repo, git_diff_file *file) +{ + const char *value; + if (git_attr_get(&value, repo, 0, file->path, "diff") < 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 0; +} + +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) + delta->binary = 1; + else if ((delta->old_file.flags & GIT_DIFF_FILE_NOT_BINARY) != 0 || + (delta->new_file.flags & GIT_DIFF_FILE_NOT_BINARY) != 0) + delta->binary = 0; + /* otherwise leave delta->binary value untouched */ +} + +static int file_is_binary_by_attr( + git_diff_list *diff, + git_diff_delta *delta) +{ + int error = 0, mirror_new; + + delta->binary = -1; + + /* make sure files are conceivably mmap-able */ + if ((git_off_t)((size_t)delta->old_file.size) != delta->old_file.size || + (git_off_t)((size_t)delta->new_file.size) != delta->new_file.size) + { + delta->old_file.flags |= GIT_DIFF_FILE_BINARY; + delta->new_file.flags |= GIT_DIFF_FILE_BINARY; + delta->binary = 1; + return 0; + } + + /* check if user is forcing us to text diff these files */ + if (diff->opts.flags & GIT_DIFF_FORCE_TEXT) { + delta->old_file.flags |= GIT_DIFF_FILE_NOT_BINARY; + delta->new_file.flags |= GIT_DIFF_FILE_NOT_BINARY; + delta->binary = 0; + return 0; + } + + /* check diff attribute +, -, or 0 */ + 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 = update_file_is_binary_by_attr(diff->repo, &delta->new_file); + + update_delta_is_binary(delta); + + return error; +} + +static int file_is_binary_by_content( + git_diff_delta *delta, + git_map *old_data, + git_map *new_data) +{ + git_buf search; + + if ((delta->old_file.flags & BINARY_DIFF_FLAGS) == 0) { + search.ptr = old_data->data; + search.size = min(old_data->len, 4000); + + if (git_buf_is_binary(&search)) + delta->old_file.flags |= GIT_DIFF_FILE_BINARY; + else + delta->old_file.flags |= GIT_DIFF_FILE_NOT_BINARY; + } + + if ((delta->new_file.flags & BINARY_DIFF_FLAGS) == 0) { + search.ptr = new_data->data; + search.size = min(new_data->len, 4000); + + if (git_buf_is_binary(&search)) + delta->new_file.flags |= GIT_DIFF_FILE_BINARY; + else + delta->new_file.flags |= GIT_DIFF_FILE_NOT_BINARY; + } + + update_delta_is_binary(delta); + + /* TODO: if value != NULL, implement diff drivers */ + + return 0; +} + +static void setup_xdiff_options( + git_diff_options *opts, xdemitconf_t *cfg, xpparam_t *param) +{ + memset(cfg, 0, sizeof(xdemitconf_t)); + memset(param, 0, sizeof(xpparam_t)); + + cfg->ctxlen = + (!opts || !opts->context_lines) ? 3 : opts->context_lines; + cfg->interhunkctxlen = + (!opts || !opts->interhunk_lines) ? 3 : opts->interhunk_lines; + + if (!opts) + return; + + if (opts->flags & GIT_DIFF_IGNORE_WHITESPACE) + param->flags |= XDF_WHITESPACE_FLAGS; + if (opts->flags & GIT_DIFF_IGNORE_WHITESPACE_CHANGE) + param->flags |= XDF_IGNORE_WHITESPACE_CHANGE; + if (opts->flags & GIT_DIFF_IGNORE_WHITESPACE_EOL) + param->flags |= XDF_IGNORE_WHITESPACE_AT_EOL; +} + +static int get_blob_content( + git_repository *repo, + const git_oid *oid, + git_map *map, + git_blob **blob) +{ + if (git_oid_iszero(oid)) + return 0; + + if (git_blob_lookup(blob, repo, oid) < 0) + return -1; + + map->data = (void *)git_blob_rawcontent(*blob); + map->len = git_blob_rawsize(*blob); + return 0; +} + +static int get_workdir_content( + git_repository *repo, + git_diff_file *file, + git_map *map) +{ + 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); + 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, path.ptr); + file->flags |= GIT_DIFF_FILE_UNMAP_DATA; + } + git_buf_free(&path); + return error; +} + +static void release_content(git_diff_file *file, git_map *map, git_blob *blob) +{ + if (blob != NULL) + git_blob_free(blob); + + if (file->flags & GIT_DIFF_FILE_FREE_DATA) { + git__free(map->data); + map->data = NULL; + file->flags &= ~GIT_DIFF_FILE_FREE_DATA; + } + else if (file->flags & GIT_DIFF_FILE_UNMAP_DATA) { + git_futils_mmap_free(map); + map->data = NULL; + file->flags &= ~GIT_DIFF_FILE_UNMAP_DATA; + } +} + +static void fill_map_from_mmfile(git_map *dst, mmfile_t *src) { + assert(dst && src); + + dst->data = src->ptr; + dst->len = src->size; +#ifdef GIT_WIN32 + dst->fmh = NULL; +#endif +} + +int git_diff_foreach( + git_diff_list *diff, + void *data, + git_diff_file_fn file_cb, + git_diff_hunk_fn hunk_cb, + git_diff_data_fn line_cb) +{ + int error = 0; + diff_output_info info; + git_diff_delta *delta; + xpparam_t xdiff_params; + xdemitconf_t xdiff_config; + xdemitcb_t xdiff_callback; + + info.diff = diff; + info.cb_data = data; + info.hunk_cb = hunk_cb; + info.line_cb = line_cb; + + setup_xdiff_options(&diff->opts, &xdiff_config, &xdiff_params); + memset(&xdiff_callback, 0, sizeof(xdiff_callback)); + xdiff_callback.outf = diff_output_cb; + xdiff_callback.priv = &info; + + git_vector_foreach(&diff->deltas, info.index, delta) { + git_blob *old_blob = NULL, *new_blob = NULL; + git_map old_data, new_data; + mmfile_t old_xdiff_data, new_xdiff_data; + + if (delta->status == GIT_DELTA_UNMODIFIED && + (diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) + continue; + + if (delta->status == GIT_DELTA_IGNORED && + (diff->opts.flags & GIT_DIFF_INCLUDE_IGNORED) == 0) + continue; + + if (delta->status == GIT_DELTA_UNTRACKED && + (diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) + continue; + + if ((error = file_is_binary_by_attr(diff, delta)) < 0) + goto cleanup; + + old_data.data = ""; + old_data.len = 0; + new_data.data = ""; + new_data.len = 0; + + /* TODO: Partial blob reading to defer loading whole blob. + * I.e. I want a blob with just the first 4kb loaded, then + * later on I will read the rest of the blob if needed. + */ + + /* map files */ + if (delta->binary != 1 && + (hunk_cb || line_cb) && + (delta->status == GIT_DELTA_DELETED || + delta->status == GIT_DELTA_MODIFIED)) + { + if (diff->old_src == GIT_ITERATOR_WORKDIR) + error = get_workdir_content(diff->repo, &delta->old_file, &old_data); + else + error = get_blob_content( + diff->repo, &delta->old_file.oid, &old_data, &old_blob); + + if (error < 0) + goto cleanup; + } + + if (delta->binary != 1 && + (hunk_cb || line_cb || git_oid_iszero(&delta->new_file.oid)) && + (delta->status == GIT_DELTA_ADDED || + delta->status == GIT_DELTA_MODIFIED)) + { + if (diff->new_src == GIT_ITERATOR_WORKDIR) + error = get_workdir_content(diff->repo, &delta->new_file, &new_data); + else + error = get_blob_content( + diff->repo, &delta->new_file.oid, &new_data, &new_blob); + + 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 < 0) + goto cleanup; + + /* since we did not have the definitive oid, we may have + * incorrect status and need to skip this item. + */ + if (git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid) == 0) { + delta->status = GIT_DELTA_UNMODIFIED; + if ((diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) + goto cleanup; + } + } + } + + /* if we have not already decided whether file is binary, + * check the first 4K for nul bytes to decide... + */ + if (delta->binary == -1) { + error = file_is_binary_by_content( + delta, &old_data, &new_data); + if (error < 0) + goto cleanup; + } + + /* TODO: if ignore_whitespace is set, then we *must* do text + * diffs to tell if a file has really been changed. + */ + + if (file_cb != NULL) { + error = file_cb(data, delta, (float)info.index / diff->deltas.length); + if (error < 0) + goto cleanup; + } + + /* don't do hunk and line diffs if file is binary */ + if (delta->binary == 1) + goto cleanup; + + /* nothing to do if we did not get data */ + if (!old_data.len && !new_data.len) + goto cleanup; + + assert(hunk_cb || line_cb); + + info.delta = delta; + old_xdiff_data.ptr = old_data.data; + old_xdiff_data.size = old_data.len; + new_xdiff_data.ptr = new_data.data; + new_xdiff_data.size = new_data.len; + + xdl_diff(&old_xdiff_data, &new_xdiff_data, + &xdiff_params, &xdiff_config, &xdiff_callback); + +cleanup: + release_content(&delta->old_file, &old_data, old_blob); + release_content(&delta->new_file, &new_data, new_blob); + + if (error < 0) + break; + } + + return error; +} + + +typedef struct { + git_diff_list *diff; + git_diff_data_fn print_cb; + void *cb_data; + git_buf *buf; +} diff_print_info; + +static char pick_suffix(int mode) +{ + if (S_ISDIR(mode)) + return '/'; + else if (mode & 0100) + /* in git, modes are very regular, so we must have 0100755 mode */ + return '*'; + else + return ' '; +} + +static int print_compact(void *data, git_diff_delta *delta, float progress) +{ + diff_print_info *pi = data; + char code, old_suffix, new_suffix; + + GIT_UNUSED(progress); + + switch (delta->status) { + case GIT_DELTA_ADDED: code = 'A'; break; + case GIT_DELTA_DELETED: code = 'D'; break; + case GIT_DELTA_MODIFIED: code = 'M'; break; + case GIT_DELTA_RENAMED: code = 'R'; break; + case GIT_DELTA_COPIED: code = 'C'; break; + case GIT_DELTA_IGNORED: code = 'I'; break; + case GIT_DELTA_UNTRACKED: code = '?'; break; + default: code = 0; + } + + if (!code) + return 0; + + old_suffix = pick_suffix(delta->old_file.mode); + new_suffix = pick_suffix(delta->new_file.mode); + + git_buf_clear(pi->buf); + + if (delta->old_file.path != delta->new_file.path && + strcmp(delta->old_file.path,delta->new_file.path) != 0) + git_buf_printf(pi->buf, "%c\t%s%c -> %s%c\n", code, + delta->old_file.path, old_suffix, delta->new_file.path, new_suffix); + else if (delta->old_file.mode != delta->new_file.mode && + delta->old_file.mode != 0 && delta->new_file.mode != 0) + git_buf_printf(pi->buf, "%c\t%s%c (%o -> %o)\n", code, + delta->old_file.path, new_suffix, delta->old_file.mode, delta->new_file.mode); + else if (old_suffix != ' ') + git_buf_printf(pi->buf, "%c\t%s%c\n", code, delta->old_file.path, old_suffix); + else + git_buf_printf(pi->buf, "%c\t%s\n", code, delta->old_file.path); + + if (git_buf_oom(pi->buf)) + return -1; + + 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_data_fn print_cb) +{ + int error; + git_buf buf = GIT_BUF_INIT; + diff_print_info pi; + + pi.diff = diff; + pi.print_cb = print_cb; + pi.cb_data = cb_data; + pi.buf = &buf; + + error = git_diff_foreach(diff, &pi, print_compact, NULL, NULL); + + git_buf_free(&buf); + + return error; +} + + +static int print_oid_range(diff_print_info *pi, git_diff_delta *delta) +{ + char start_oid[8], end_oid[8]; + + /* TODO: Determine a good actual OID range to print */ + git_oid_tostr(start_oid, sizeof(start_oid), &delta->old_file.oid); + git_oid_tostr(end_oid, sizeof(end_oid), &delta->new_file.oid); + + /* TODO: Match git diff more closely */ + if (delta->old_file.mode == delta->new_file.mode) { + git_buf_printf(pi->buf, "index %s..%s %o\n", + start_oid, end_oid, delta->old_file.mode); + } else { + if (delta->old_file.mode == 0) { + git_buf_printf(pi->buf, "new file mode %o\n", delta->new_file.mode); + } else if (delta->new_file.mode == 0) { + git_buf_printf(pi->buf, "deleted file mode %o\n", delta->old_file.mode); + } else { + git_buf_printf(pi->buf, "old mode %o\n", delta->old_file.mode); + git_buf_printf(pi->buf, "new mode %o\n", delta->new_file.mode); + } + git_buf_printf(pi->buf, "index %s..%s\n", start_oid, end_oid); + } + + if (git_buf_oom(pi->buf)) + return -1; + + return 0; +} + +static int print_patch_file(void *data, git_diff_delta *delta, float progress) +{ + 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 (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)) { + newpfx = ""; + newpath = "/dev/null"; + } + + if (delta->binary != 1) { + git_buf_printf(pi->buf, "--- %s%s\n", oldpfx, oldpath); + git_buf_printf(pi->buf, "+++ %s%s\n", newpfx, newpath); + } + + if (git_buf_oom(pi->buf)) + return -1; + + 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_oom(pi->buf)) + return -1; + + 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( + void *data, + git_diff_delta *d, + git_diff_range *r, + const char *header, + size_t header_len) +{ + diff_print_info *pi = data; + + git_buf_clear(pi->buf); + if (git_buf_printf(pi->buf, "%.*s", (int)header_len, header) < 0) + return -1; + + 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_buf_clear(pi->buf); + + if (line_origin == GIT_DIFF_LINE_ADDITION || + line_origin == GIT_DIFF_LINE_DELETION || + line_origin == GIT_DIFF_LINE_CONTEXT) + git_buf_printf(pi->buf, "%c%.*s", line_origin, (int)content_len, content); + else if (content_len > 0) + git_buf_printf(pi->buf, "%.*s", (int)content_len, content); + + if (git_buf_oom(pi->buf)) + return -1; + + 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_data_fn print_cb) +{ + int error; + git_buf buf = GIT_BUF_INIT; + diff_print_info pi; + + pi.diff = diff; + pi.print_cb = print_cb; + pi.cb_data = cb_data; + pi.buf = &buf; + + error = git_diff_foreach( + diff, &pi, print_patch_file, print_patch_hunk, print_patch_line); + + git_buf_free(&buf); + + return error; +} + +int git_diff_blobs( + git_blob *old_blob, + git_blob *new_blob, + git_diff_options *options, + void *cb_data, + git_diff_file_fn file_cb, + git_diff_hunk_fn hunk_cb, + git_diff_data_fn line_cb) +{ + diff_output_info info; + git_diff_delta delta; + mmfile_t old_data, new_data; + git_map old_map, new_map; + xpparam_t xdiff_params; + xdemitconf_t xdiff_config; + xdemitcb_t xdiff_callback; + git_blob *new, *old; + + memset(&delta, 0, sizeof(delta)); + + new = new_blob; + old = old_blob; + + if (options && (options->flags & GIT_DIFF_REVERSE)) { + git_blob *swap = old; + old = new; + new = swap; + } + + if (old) { + old_data.ptr = (char *)git_blob_rawcontent(old); + old_data.size = git_blob_rawsize(old); + git_oid_cpy(&delta.old_file.oid, git_object_id((const git_object *)old)); + } else { + old_data.ptr = ""; + old_data.size = 0; + } + + if (new) { + new_data.ptr = (char *)git_blob_rawcontent(new); + new_data.size = git_blob_rawsize(new); + git_oid_cpy(&delta.new_file.oid, git_object_id((const git_object *)new)); + } else { + new_data.ptr = ""; + new_data.size = 0; + } + + /* populate a "fake" delta record */ + delta.status = new ? + (old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) : + (old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED); + + if (git_oid_cmp(&delta.new_file.oid, &delta.old_file.oid) == 0) + delta.status = GIT_DELTA_UNMODIFIED; + + delta.old_file.size = old_data.size; + delta.new_file.size = new_data.size; + + fill_map_from_mmfile(&old_map, &old_data); + fill_map_from_mmfile(&new_map, &new_data); + + if (file_is_binary_by_content(&delta, &old_map, &new_map) < 0) + return -1; + + if (file_cb != NULL) { + int error = file_cb(cb_data, &delta, 1); + if (error < 0) + return error; + } + + /* don't do hunk and line diffs if the two blobs are identical */ + if (delta.status == GIT_DELTA_UNMODIFIED) + return 0; + + /* don't do hunk and line diffs if file is binary */ + if (delta.binary == 1) + return 0; + + info.diff = NULL; + info.delta = δ + info.cb_data = cb_data; + info.hunk_cb = hunk_cb; + info.line_cb = line_cb; + + setup_xdiff_options(options, &xdiff_config, &xdiff_params); + memset(&xdiff_callback, 0, sizeof(xdiff_callback)); + xdiff_callback.outf = diff_output_cb; + xdiff_callback.priv = &info; + + xdl_diff(&old_data, &new_data, &xdiff_params, &xdiff_config, &xdiff_callback); + + return 0; +} diff --git a/src/dir.h b/src/dir.h deleted file mode 100644 index c01c3fae7..000000000 --- a/src/dir.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef INCLUDE_dir_h__ -#define INCLUDE_dir_h__ - -#include "common.h" - -#ifndef GIT_WIN32 -# include <dirent.h> -#endif - -#ifdef GIT_WIN32 - -struct git__dirent { - int d_ino; - char d_name[261]; -}; - -typedef struct { - HANDLE h; - WIN32_FIND_DATA f; - struct git__dirent entry; - char *dir; - int first; -} git__DIR; - -extern git__DIR *git__opendir(const char *); -extern struct git__dirent *git__readdir(git__DIR *); -extern void git__rewinddir(git__DIR *); -extern int git__closedir(git__DIR *); - -# ifndef GIT__WIN32_NO_WRAP_DIR -# define dirent git__dirent -# define DIR git__DIR -# define opendir git__opendir -# define readdir git__readdir -# define rewinddir git__rewinddir -# define closedir git__closedir -# endif - -#endif - -#endif /* INCLUDE_dir_h__ */ diff --git a/src/errors.c b/src/errors.c index 7da02b4f7..d43d7d9b5 100644 --- a/src/errors.c +++ b/src/errors.c @@ -1,113 +1,119 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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/thread-utils.h" /* for GIT_TLS */ -#include "thread-utils.h" /* for GIT_TLS */ - +#include "global.h" +#include "posix.h" +#include "buffer.h" #include <stdarg.h> -static GIT_TLS char g_last_error[1024]; - -static struct { - int num; - const char *str; -} error_codes[] = { - {GIT_ERROR, "Unspecified error"}, - {GIT_ENOTOID, "Input was not a properly formatted Git object id."}, - {GIT_ENOTFOUND, "Object does not exist in the scope searched."}, - {GIT_ENOMEM, "Not enough space available."}, - {GIT_EOSERR, "Consult the OS error information."}, - {GIT_EOBJTYPE, "The specified object is of invalid type"}, - {GIT_EOBJCORRUPTED, "The specified object has its data corrupted"}, - {GIT_ENOTAREPO, "The specified repository is invalid"}, - {GIT_EINVALIDTYPE, "The object or config variable type is invalid or doesn't match"}, - {GIT_EMISSINGOBJDATA, "The object cannot be written that because it's missing internal data"}, - {GIT_EPACKCORRUPTED, "The packfile for the ODB is corrupted"}, - {GIT_EFLOCKFAIL, "Failed to adquire or release a file lock"}, - {GIT_EZLIB, "The Z library failed to inflate/deflate an object's data"}, - {GIT_EBUSY, "The queried object is currently busy"}, - {GIT_EINVALIDPATH, "The path is invalid"}, - {GIT_EBAREINDEX, "The index file is not backed up by an existing repository"}, - {GIT_EINVALIDREFNAME, "The name of the reference is not valid"}, - {GIT_EREFCORRUPTED, "The specified reference has its data corrupted"}, - {GIT_ETOONESTEDSYMREF, "The specified symbolic reference is too deeply nested"}, - {GIT_EPACKEDREFSCORRUPTED, "The pack-refs file is either corrupted of its format is not currently supported"}, - {GIT_EINVALIDPATH, "The path is invalid" }, - {GIT_EREVWALKOVER, "The revision walker is empty; there are no more commits left to iterate"}, - {GIT_EINVALIDREFSTATE, "The state of the reference is not valid"}, - {GIT_ENOTIMPLEMENTED, "This feature has not been implemented yet"}, - {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"}, +/******************************************** + * New error handling + ********************************************/ + +static git_error g_git_oom_error = { + "Out of memory", + GITERR_NOMEMORY }; -const char *git_strerror(int num) +static void set_error(int error_class, char *string) { - size_t i; + git_error *error = &GIT_GLOBAL->error_t; - if (num == GIT_EOSERR) - return strerror(errno); - for (i = 0; i < ARRAY_SIZE(error_codes); i++) - if (num == error_codes[i].num) - return error_codes[i].str; + git__free(error->message); - return "Unknown error"; + error->message = string; + error->klass = error_class; + + GIT_GLOBAL->last_error = error; } -int git__rethrow(int error, const char *msg, ...) +void giterr_set_oom(void) { - char new_error[1024]; - char *old_error = NULL; - - va_list va; + GIT_GLOBAL->last_error = &g_git_oom_error; +} - va_start(va, msg); - vsnprintf(new_error, sizeof(new_error), msg, va); - va_end(va); +void giterr_set(int error_class, const char *string, ...) +{ + git_buf buf = GIT_BUF_INIT; + va_list arglist; + + int unix_error_code = 0; + +#ifdef GIT_WIN32 + DWORD win32_error_code = 0; +#endif + + if (error_class == GITERR_OS) { + unix_error_code = errno; + errno = 0; + +#ifdef GIT_WIN32 + win32_error_code = GetLastError(); + SetLastError(0); +#endif + } + + va_start(arglist, string); + git_buf_vprintf(&buf, string, arglist); + va_end(arglist); + + /* automatically suffix strerror(errno) for GITERR_OS errors */ + if (error_class == GITERR_OS) { + + if (unix_error_code != 0) { + git_buf_PUTS(&buf, ": "); + git_buf_puts(&buf, strerror(unix_error_code)); + } + +#ifdef GIT_WIN32 + else if (win32_error_code != 0) { + LPVOID lpMsgBuf = NULL; + + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, win32_error_code, 0, (LPTSTR) &lpMsgBuf, 0, NULL); + + if (lpMsgBuf) { + git_buf_PUTS(&buf, ": "); + git_buf_puts(&buf, lpMsgBuf); + LocalFree(lpMsgBuf); + } + } +#endif + } + + if (!git_buf_oom(&buf)) + set_error(error_class, git_buf_detach(&buf)); +} - old_error = strdup(g_last_error); - snprintf(g_last_error, sizeof(g_last_error), "%s \n - %s", new_error, old_error); - free(old_error); +void giterr_set_str(int error_class, const char *string) +{ + char *message = git__strdup(string); - return error; + if (message) + set_error(error_class, message); } -int git__throw(int error, const char *msg, ...) +void giterr_set_regex(const regex_t *regex, int error_code) { - va_list va; - - va_start(va, msg); - vsnprintf(g_last_error, sizeof(g_last_error), msg, va); - va_end(va); + char error_buf[1024]; + regerror(error_code, regex, error_buf, sizeof(error_buf)); + giterr_set_str(GITERR_REGEX, error_buf); +} - return error; +void giterr_clear(void) +{ + GIT_GLOBAL->last_error = NULL; } -const char *git_lasterror(void) +const git_error *giterr_last(void) { - return g_last_error; + return GIT_GLOBAL->last_error; } diff --git a/src/fetch.c b/src/fetch.c new file mode 100644 index 000000000..96b263faa --- /dev/null +++ b/src/fetch.c @@ -0,0 +1,200 @@ +/* + * 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 "git2/remote.h" +#include "git2/oid.h" +#include "git2/refs.h" +#include "git2/revwalk.h" +#include "git2/indexer.h" + +#include "common.h" +#include "transport.h" +#include "remote.h" +#include "refspec.h" +#include "pack.h" +#include "fetch.h" +#include "netops.h" + +struct filter_payload { + git_remote *remote; + const git_refspec *spec; + git_odb *odb; + int found_head; +}; + +static int filter_ref__cb(git_remote_head *head, void *payload) +{ + struct filter_payload *p = payload; + + 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 */ + if (!git_refspec_src_matches(p->spec, head->name)) + return 0; + + /* Don't even try to ask for the annotation target */ + if (!git__suffixcmp(head->name, "^{}")) + return 0; + } + + /* If we have the object, mark it so we don't ask for it */ + if (git_odb_exists(p->odb, &head->oid)) + head->local = 1; + else + p->remote->need_pack = 1; + + return git_vector_insert(&p->remote->refs, head); +} + +static int filter_wants(git_remote *remote) +{ + struct filter_payload p; + + git_vector_clear(&remote->refs); + + /* + * The fetch refspec can be NULL, and what this means is that the + * user didn't specify one. This is fine, as it means that we're + * not interested in any particular branch but just the remote's + * HEAD, which will be stored in FETCH_HEAD after the fetch. + */ + p.spec = git_remote_fetchspec(remote); + p.found_head = 0; + p.remote = remote; + + if (git_repository_odb__weakptr(&p.odb, remote->repo) < 0) + return -1; + + return remote->transport->ls(remote->transport, &filter_ref__cb, &p); +} + +/* + * In this first version, we push all our refs in and start sending + * them out. When we get an ACK we hide that commit and continue + * traversing until we're done + */ +int git_fetch_negotiate(git_remote *remote) +{ + git_transport *t = remote->transport; + + 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 || !remote->need_pack) + return 0; + + /* + * Now we have everything set up so we can start tell the server + * what we want and what we have. + */ + return t->negotiate_fetch(t, remote->repo, &remote->refs); +} + +int git_fetch_download_pack(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats) +{ + if(!remote->need_pack) + return 0; + + 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( + const char *buffered, + size_t buffered_size, + git_transport *t, + git_repository *repo, + git_off_t *bytes, + git_indexer_stats *stats) +{ + int recvd; + char buff[1024]; + gitno_buffer buf; + git_indexer_stream *idx; + + gitno_buffer_setup(t, &buf, buff, sizeof(buff)); + + if (memcmp(buffered, "PACK", strlen("PACK"))) { + giterr_set(GITERR_NET, "The pack doesn't start with the signature"); + return -1; + } + + if (git_indexer_stream_new(&idx, git_repository_path(repo)) < 0) + return -1; + + memset(stats, 0, sizeof(git_indexer_stats)); + if (git_indexer_stream_add(idx, buffered, buffered_size, stats) < 0) + goto on_error; + + *bytes = buffered_size; + + do { + if (git_indexer_stream_add(idx, buf.data, buf.offset, stats) < 0) + goto on_error; + + gitno_consume_n(&buf, buf.offset); + 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_list(&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; + + git_reference_free(ref); + } + + git_strarray_free(&refs); + *out = walk; + return 0; + +on_error: + git_reference_free(ref); + git_strarray_free(&refs); + return -1; +} diff --git a/src/fetch.h b/src/fetch.h new file mode 100644 index 000000000..a7f126520 --- /dev/null +++ b/src/fetch.h @@ -0,0 +1,19 @@ +/* + * 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_fetch_h__ +#define INCLUDE_fetch_h__ + +#include "netops.h" + +int git_fetch_negotiate(git_remote *remote); +int git_fetch_download_pack(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats); + +int git_fetch__download_pack(const char *buffered, size_t buffered_size, git_transport *t, + 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 63f2897cb..876f8e3e7 100644 --- a/src/filebuf.c +++ b/src/filebuf.c @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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 <stdarg.h> @@ -28,68 +10,117 @@ #include "filebuf.h" #include "fileops.h" +#define GIT_LOCK_FILE_MODE 0644 + 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 (gitfo_exists(file->path_lock) == 0) { + if (git_path_exists(file->path_lock) == true) { if (flags & GIT_FILEBUF_FORCE) - gitfo_unlink(file->path_lock); - else - return git__throw(GIT_EOSERR, "Failed to lock file"); + p_unlink(file->path_lock); + 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 */ if (flags & GIT_FILEBUF_FORCE) { - file->fd = gitfo_creat_force(file->path_lock, 0644); + /* XXX: Should dirmode here be configurable? Or is 0777 always fine? */ + file->fd = git_futils_creat_locked_withpath(file->path_lock, 0777, GIT_LOCK_FILE_MODE); } else { - file->fd = gitfo_creat(file->path_lock, 0644); + file->fd = git_futils_creat_locked(file->path_lock, GIT_LOCK_FILE_MODE); } if (file->fd < 0) - return git__throw(GIT_EOSERR, "Failed to create lock"); + return -1; - /* TODO: do a flock() in the descriptor file_lock */ + file->fd_is_open = true; - if ((flags & GIT_FILEBUF_APPEND) && gitfo_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 = gitfo_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); + source = p_open(file->path_original, O_RDONLY); + if (source < 0) { + giterr_set(GITERR_OS, + "Failed to open file '%s' for reading", + file->path_original); + return -1; + } - while ((read_bytes = gitfo_read(source, buffer, 2048)) > 0) { - gitfo_write(file->fd, buffer, read_bytes); + while ((read_bytes = p_read(source, buffer, 2048)) > 0) { + p_write(file->fd, buffer, read_bytes); if (file->digest) git_hash_update(file->digest, buffer, read_bytes); } - gitfo_close(source); + p_close(source); } - return GIT_SUCCESS; + return 0; } void git_filebuf_cleanup(git_filebuf *file) { - if (file->fd >= 0) - gitfo_close(file->fd); + if (file->fd_is_open && file->fd >= 0) + p_close(file->fd); - if (file->path_lock && gitfo_exists(file->path_lock) == GIT_SUCCESS) - gitfo_unlink(file->path_lock); + if (file->fd_is_open && file->path_lock && git_path_exists(file->path_lock)) + p_unlink(file->path_lock); if (file->digest) git_hash_free_ctx(file->digest); - free(file->buffer); - free(file->z_buf); + if (file->buffer) + git__free(file->buffer); + + /* use the presence of z_buf to decide if we need to deflateEnd */ + if (file->z_buf) { + git__free(file->z_buf); + deflateEnd(&file->zs); + } - deflateEnd(&file->zs); + if (file->path_original) + git__free(file->path_original); + if (file->path_lock) + git__free(file->path_lock); - free(file->path_original); - free(file->path_lock); + memset(file, 0x0, sizeof(git_filebuf)); + file->fd = -1; } GIT_INLINE(int) flush_buffer(git_filebuf *file) @@ -99,43 +130,53 @@ GIT_INLINE(int) flush_buffer(git_filebuf *file) return result; } -static int write_normal(git_filebuf *file, const void *source, size_t len) +int git_filebuf_flush(git_filebuf *file) { - int result = 0; + return flush_buffer(file); +} +static int write_normal(git_filebuf *file, void *source, size_t len) +{ if (len > 0) { - result = gitfo_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, const void *source, size_t len) +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) { - zs->next_in = (void *)source; - zs->avail_in = len; + zs->next_in = source; + zs->avail_in = (uInt)len; do { - int have; + size_t have; zs->next_out = file->z_buf; - zs->avail_out = file->buf_size; + zs->avail_out = (uInt)file->buf_size; - result = deflate(zs, file->flush_mode); - assert(result != Z_STREAM_ERROR); + if (deflate(zs, file->flush_mode) == Z_STREAM_ERROR) { + file->last_error = BUFERR_ZLIB; + return -1; + } - have = file->buf_size - zs->avail_out; + have = file->buf_size - (size_t)zs->avail_out; - if (gitfo_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); + } while (zs->avail_out == 0); assert(zs->avail_in == 0); @@ -143,52 +184,54 @@ static int write_deflate(git_filebuf *file, const 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; + int compression; size_t path_len; - assert(file && path); + /* 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); } - /* If we are deflating on-write, */ - if (flags & GIT_FILEBUF_DEFLATE_CONTENTS) { + compression = flags >> GIT_FILEBUF_DEFLATE_SHIFT; + /* If we are deflating on-write, */ + if (compression != 0) { /* Initialize the ZLib stream */ - if (deflateInit(&file->zs, Z_BEST_SPEED) != Z_OK) { - error = git__throw(GIT_EZLIB, "Failed to initialize zlib"); + if (deflateInit(&file->zs, compression) != Z_OK) { + 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; @@ -199,102 +242,105 @@ int git_filebuf_open(git_filebuf *file, const char *path, int flags) /* If we are writing to a temp file */ if (flags & GIT_FILEBUF_TEMPORARY) { - char tmp_path[GIT_PATH_MAX]; + git_buf tmp_path = GIT_BUF_INIT; /* Open the file as temporary for locking */ - file->fd = gitfo_mktemp(tmp_path, path); + file->fd = git_futils_mktmp(&tmp_path, path); + if (file->fd < 0) { - error = GIT_EOSERR; + git_buf_free(&tmp_path); goto cleanup; } + file->fd_is_open = true; /* No original path */ file->path_original = NULL; - file->path_lock = git__strdup(tmp_path); - - if (file->path_lock == NULL) { - error = GIT_ENOMEM; - goto cleanup; - } + file->path_lock = git_buf_detach(&tmp_path); + 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) +int git_filebuf_commit_at(git_filebuf *file, const char *path, mode_t mode) { - free(file->path_original); + 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); + return git_filebuf_commit(file, mode); } -int git_filebuf_commit(git_filebuf *file) +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; - gitfo_close(file->fd); + p_close(file->fd); file->fd = -1; + file->fd_is_open = false; - error = gitfo_mv(file->path_lock, file->path_original); + if (p_chmod(file->path_lock, mode)) { + giterr_set(GITERR_OS, "Failed to set attributes for file at '%s'", file->path_lock); + goto on_error; + } -cleanup: + p_unlink(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; + +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) @@ -305,80 +351,107 @@ 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; } - /* flush the cache if it doesn't fit */ - if (file->buf_pos > 0) { - add_to_cache(file, buf, space_left); - - if ((error = flush_buffer(file)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to write to buffer"); - - len -= space_left; - buf += space_left; - } + add_to_cache(file, buf, space_left); + if (flush_buffer(file) < 0) + return -1; - /* write too-large chunks immediately */ - if (len > file->buf_size) { - error = file->write(file, buf, len); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to write to buffer"); - } + len -= space_left; + buf += space_left; } } 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 = file->buf_size - file->buf_pos; - int len, error; + size_t space_left; + int len, res; + char *tmp_buffer; - va_start(arglist, format); + ENSURE_BUF_OK(file); - len = vsnprintf((char *)file->buffer + file->buf_pos, space_left, format, arglist); + space_left = file->buf_size - file->buf_pos; - if (len < 0 || (size_t)len >= space_left) { - if ((error = flush_buffer(file)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to output to buffer"); + do { + va_start(arglist, format); + len = p_vsnprintf((char *)file->buffer + file->buf_pos, space_left, format, arglist); + va_end(arglist); + + if (len < 0) { + file->last_error = BUFERR_MEM; + return -1; + } + + if ((size_t)len + 1 <= space_left) { + file->buf_pos += len; + return 0; + } - len = vsnprintf((char *)file->buffer + file->buf_pos, space_left, format, arglist); - if (len < 0 || (size_t)len > file->buf_size) - return GIT_ENOMEM; + 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) { + file->last_error = BUFERR_MEM; + return -1; } - file->buf_pos += len; - return GIT_SUCCESS; + va_start(arglist, format); + len = p_vsnprintf(tmp_buffer, len + 1, format, arglist); + va_end(arglist); + + if (len < 0) { + git__free(tmp_buffer); + file->last_error = BUFERR_MEM; + return -1; + } + + res = git_filebuf_write(file, tmp_buffer, len); + git__free(tmp_buffer); + return res; } diff --git a/src/filebuf.h b/src/filebuf.h index 37cb36784..377883147 100644 --- a/src/filebuf.h +++ b/src/filebuf.h @@ -1,9 +1,15 @@ +/* + * 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_filebuf_h__ #define INCLUDE_filebuf_h__ #include "fileops.h" #include "hash.h" -#include "git2/zlib.h" +#include <zlib.h> #ifdef GIT_THREADS # define GIT_FILEBUF_THREADS @@ -13,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_CONTENTS (1 << 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 @@ -22,8 +29,7 @@ struct git_filebuf { char *path_original; char *path_lock; - int (*write)(struct git_filebuf *file, - const void *source, size_t len); + int (*write)(struct git_filebuf *file, void *source, size_t len); git_hash_ctx *digest; @@ -35,18 +41,46 @@ 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: + * - 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. 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. 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 + * 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, ...); +int git_filebuf_printf(git_filebuf *file, const char *format, ...) GIT_FORMAT_PRINTF(2, 3); int git_filebuf_open(git_filebuf *lock, const char *path, int flags); -int git_filebuf_commit(git_filebuf *lock); -int git_filebuf_commit_at(git_filebuf *lock, const char *path); +int git_filebuf_commit(git_filebuf *lock, mode_t mode); +int git_filebuf_commit_at(git_filebuf *lock, const char *path, mode_t mode); void git_filebuf_cleanup(git_filebuf *lock); int git_filebuf_hash(git_oid *oid, git_filebuf *file); +int git_filebuf_flush(git_filebuf *file); #endif diff --git a/src/fileops.c b/src/fileops.c index 73939349d..95a65893c 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -1,350 +1,283 @@ +/* + * 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 "fileops.h" #include <ctype.h> -int gitfo_mkdir_2file(const char *file_path) +int git_futils_mkpath2file(const char *file_path, const mode_t mode) { - const int mode = 0755; /* or 0777 ? */ - int error = GIT_SUCCESS; - char target_folder_path[GIT_PATH_MAX]; + int result = 0; + git_buf target_folder = GIT_BUF_INIT; - error = git__dirname_r(target_folder_path, sizeof(target_folder_path), file_path); - if (error < GIT_SUCCESS) - return git__throw(GIT_EINVALIDPATH, "Failed to recursively build `%s` tree structure. Unable to parse parent folder name", file_path); + if (git_path_dirname_r(&target_folder, file_path) < 0) + return -1; /* Does the containing folder exist? */ - if (gitfo_isdir(target_folder_path)) { - git__joinpath(target_folder_path, target_folder_path, ""); /* Ensure there's a trailing slash */ - + if (git_path_isdir(target_folder.ptr) == false) /* Let's create the tree structure */ - error = gitfo_mkdir_recurs(target_folder_path, mode); - if (error < GIT_SUCCESS) - return error; /* The callee already takes care of setting the correct error message. */ - } + result = git_futils_mkdir_r(target_folder.ptr, NULL, mode); - return GIT_SUCCESS; + git_buf_free(&target_folder); + return result; } -int gitfo_mktemp(char *path_out, const char *filename) +int git_futils_mktmp(git_buf *path_out, const char *filename) { int fd; - strcpy(path_out, filename); - strcat(path_out, "_git2_XXXXXX"); - -#if defined(_MSC_VER) - /* FIXME: there may be race conditions when multi-threading - * with the library */ - if (_mktemp_s(path_out, GIT_PATH_MAX) != 0) - return git__throw(GIT_EOSERR, "Failed to make temporary file %s", path_out); - - fd = gitfo_creat(path_out, 0744); -#else - fd = mkstemp(path_out); -#endif + git_buf_sets(path_out, filename); + git_buf_puts(path_out, "_git2_XXXXXX"); - return fd >= 0 ? fd : git__throw(GIT_EOSERR, "Failed to create temporary file %s", path_out); -} + if (git_buf_oom(path_out)) + return -1; -int gitfo_open(const char *path, int flags) -{ - int fd = open(path, flags | O_BINARY); - return fd >= 0 ? fd : git__throw(GIT_EOSERR, "Failed to open %s", path); -} + if ((fd = p_mkstemp(path_out->ptr)) < 0) { + giterr_set(GITERR_OS, + "Failed to create temporary file '%s'", path_out->ptr); + return -1; + } -int gitfo_creat(const char *path, int mode) -{ - int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, mode); - return fd >= 0 ? fd : git__throw(GIT_EOSERR, "Failed to create file. Could not open %s", path); + return fd; } -int gitfo_creat_force(const char *path, int mode) +int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode) { - if (gitfo_mkdir_2file(path) < GIT_SUCCESS) - return git__throw(GIT_EOSERR, "Failed to create file %s", path); + int fd; - return gitfo_creat(path, mode); -} + if (git_futils_mkpath2file(path, dirmode) < 0) + return -1; -int gitfo_read(git_file fd, void *buf, size_t cnt) -{ - char *b = buf; - while (cnt) { - ssize_t r = read(fd, b, cnt); - if (r < 0) { - if (errno == EINTR || errno == EAGAIN) - continue; - return git__throw(GIT_EOSERR, "Failed to read from file"); - } - if (!r) { - errno = EPIPE; - return git__throw(GIT_EOSERR, "Failed to read from file"); - } - cnt -= r; - b += r; + fd = p_creat(path, mode); + if (fd < 0) { + giterr_set(GITERR_OS, "Failed to create file '%s'", path); + return -1; } - return GIT_SUCCESS; -} -int gitfo_write(git_file fd, void *buf, size_t cnt) -{ - char *b = buf; - while (cnt) { - ssize_t r = write(fd, b, cnt); - if (r < 0) { - if (errno == EINTR || errno == EAGAIN) - continue; - return git__throw(GIT_EOSERR, "Failed to write to file"); - } - if (!r) { - errno = EPIPE; - return git__throw(GIT_EOSERR, "Failed to write to file"); - } - cnt -= r; - b += r; - } - return GIT_SUCCESS; + return fd; } -int gitfo_isdir(const char *path) +int git_futils_creat_locked(const char *path, const mode_t mode) { - struct stat st; - int len, stat_error; + int fd; - if (!path) - return git__throw(GIT_ENOTFOUND, "No path given to gitfo_isdir"); +#ifdef GIT_WIN32 + wchar_t* buf; - len = strlen(path); + 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 - /* win32: stat path for folders cannot end in a slash */ - if (path[len - 1] == '/') { - char *path_fixed = NULL; - path_fixed = git__strdup(path); - path_fixed[len - 1] = 0; - stat_error = gitfo_stat(path_fixed, &st); - free(path_fixed); - } else { - stat_error = gitfo_stat(path, &st); + if (fd < 0) { + giterr_set(GITERR_OS, "Failed to create locked file '%s'", path); + return -1; } - if (stat_error < GIT_SUCCESS) - return git__throw(GIT_ENOTFOUND, "%s does not exist", path); + return fd; +} - if (!S_ISDIR(st.st_mode)) - return git__throw(GIT_ENOTFOUND, "%s is a file", path); +int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode) +{ + if (git_futils_mkpath2file(path, dirmode) < 0) + return -1; - return GIT_SUCCESS; + return git_futils_creat_locked(path, mode); } -int gitfo_exists(const char *path) +int git_futils_open_ro(const char *path) { - assert(path); - return access(path, F_OK); + 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 gitfo_size(git_file fd) +git_off_t git_futils_filesize(git_file fd) { struct stat sb; - if (gitfo_fstat(fd, &sb)) - return git__throw(GIT_EOSERR, "Failed to get size of file. File missing or corrupted"); + + if (p_fstat(fd, &sb)) { + giterr_set(GITERR_OS, "Failed to stat file descriptor"); + return -1; + } + return sb.st_size; } -int gitfo_read_file(gitfo_buf *obj, const char *path) +mode_t git_futils_canonical_mode(mode_t raw_mode) +{ + if (S_ISREG(raw_mode)) + return S_IFREG | GIT_CANONICAL_PERMS(raw_mode); + else if (S_ISLNK(raw_mode)) + return S_IFLNK; + else if (S_ISGITLINK(raw_mode)) + return S_IFGITLINK; + else if (S_ISDIR(raw_mode)) + return S_IFDIR; + else + return 0; +} + +int git_futils_readbuffer_updated(git_buf *buf, const char *path, time_t *mtime, int *updated) { git_file fd; size_t len; - git_off_t size; - unsigned char *buff; + struct stat st; - assert(obj && path && *path); + assert(buf && path && *path); - if ((fd = gitfo_open(path, O_RDONLY)) < 0) - return git__throw(GIT_ERROR, "Failed to open %s for reading", path); + if (updated != NULL) + *updated = 0; - if (((size = gitfo_size(fd)) < 0) || !git__is_sizet(size+1)) { - gitfo_close(fd); - return git__throw(GIT_ERROR, "Failed to read file `%s`. An error occured while calculating its size", path); - } - len = (size_t) size; + if ((fd = git_futils_open_ro(path)) < 0) + return fd; - if ((buff = git__malloc(len + 1)) == NULL) { - gitfo_close(fd); - return GIT_ENOMEM; + if (p_fstat(fd, &st) < 0 || S_ISDIR(st.st_mode) || !git__is_sizet(st.st_size+1)) { + p_close(fd); + giterr_set(GITERR_OS, "Invalid regular file stat for '%s'", path); + return -1; } - if (gitfo_read(fd, buff, len) < 0) { - gitfo_close(fd); - free(buff); - return git__throw(GIT_ERROR, "Failed to read file `%s`", path); + /* + * If we were given a time, we only want to read the file if it + * has been modified. + */ + if (mtime != NULL && *mtime >= st.st_mtime) { + p_close(fd); + return 0; } - buff[len] = '\0'; - gitfo_close(fd); + if (mtime != NULL) + *mtime = st.st_mtime; - obj->data = buff; - obj->len = len; + len = (size_t) st.st_size; - return GIT_SUCCESS; -} - -void gitfo_free_buf(gitfo_buf *obj) -{ - assert(obj); - free(obj->data); - obj->data = NULL; -} + git_buf_clear(buf); -int gitfo_mv(const char *from, const char *to) -{ - int error; - -#ifdef GIT_WIN32 - /* - * Win32 POSIX compilance my ass. If the destination - * file exists, the `rename` call fails. This is as - * close as it gets with the Win32 API. - */ - error = MoveFileEx(from, to, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) ? GIT_SUCCESS : GIT_EOSERR; -#else - /* Don't even try this on Win32 */ - if (!link(from, to)) { - gitfo_unlink(from); - return GIT_SUCCESS; + if (git_buf_grow(buf, len + 1) < 0) { + p_close(fd); + return -1; } - if (!rename(from, to)) - return GIT_SUCCESS; + buf->ptr[len] = '\0'; - error = GIT_EOSERR; -#endif + while (len > 0) { + ssize_t read_size = p_read(fd, buf->ptr, len); - if (error < GIT_SUCCESS) - return git__throw(error, "Failed to move file from `%s` to `%s`", from, to); + if (read_size < 0) { + p_close(fd); + giterr_set(GITERR_OS, "Failed to read descriptor for '%s'", path); + return -1; + } - return GIT_SUCCESS; -} + len -= read_size; + buf->size += read_size; + } -int gitfo_mv_force(const char *from, const char *to) -{ - if (gitfo_mkdir_2file(to) < GIT_SUCCESS) - return GIT_EOSERR; /* The callee already takes care of setting the correct error message. */ + p_close(fd); - return gitfo_mv(from, to); /* The callee already takes care of setting the correct error message. */ -} + if (updated != NULL) + *updated = 1; -int gitfo_map_ro(git_map *out, git_file fd, git_off_t begin, size_t len) -{ - if (git__mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin) < GIT_SUCCESS) - return GIT_EOSERR; - return GIT_SUCCESS; + return 0; } -void gitfo_free_map(git_map *out) +int git_futils_readbuffer(git_buf *buf, const char *path) { - git__munmap(out); + return git_futils_readbuffer_updated(buf, path, NULL, NULL); } -int gitfo_dirent( - char *path, - size_t path_sz, - int (*fn)(void *, char *), - void *arg) +int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode) { - size_t wd_len = strlen(path); - DIR *dir; - struct dirent *de; - - if (!wd_len || path_sz < wd_len + 2) - return git__throw(GIT_EINVALIDARGS, "Failed to process `%s` tree structure. Path is either empty or buffer size is too short", path); - - while (path[wd_len - 1] == '/') - wd_len--; - path[wd_len++] = '/'; - path[wd_len] = '\0'; - - dir = opendir(path); - if (!dir) - return git__throw(GIT_EOSERR, "Failed to process `%s` tree structure. An error occured while opening the directory", path); - - while ((de = readdir(dir)) != NULL) { - size_t de_len; - int result; - - /* always skip '.' and '..' */ - if (de->d_name[0] == '.') { - if (de->d_name[1] == '\0') - continue; - if (de->d_name[1] == '.' && de->d_name[2] == '\0') - continue; - } + if (git_futils_mkpath2file(to, dirmode) < 0) + return -1; - de_len = strlen(de->d_name); - if (path_sz < wd_len + de_len + 1) { - closedir(dir); - return git__throw(GIT_ERROR, "Failed to process `%s` tree structure. Buffer size is too short", path); - } - - strcpy(path + wd_len, de->d_name); - result = fn(arg, path); - if (result < GIT_SUCCESS) { - closedir(dir); - return result; /* The callee is reponsible for setting the correct error message */ - } - if (result > 0) { - closedir(dir); - return result; - } + if (p_rename(from, to) < 0) { + giterr_set(GITERR_OS, "Failed to rename '%s' to '%s'", from, to); + return -1; } - closedir(dir); - return GIT_SUCCESS; + return 0; } - -int retrieve_path_root_offset(const char *path) +int git_futils_mmap_ro(git_map *out, git_file fd, git_off_t begin, size_t len) { - int offset = 0; - -#ifdef GIT_WIN32 + return p_mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin); +} - /* Does the root of the path look like a windows drive ? */ - if (isalpha(path[0]) && (path[1] == ':')) - offset += 2; +int git_futils_mmap_ro_file(git_map *out, const char *path) +{ + git_file fd = git_futils_open_ro(path); + git_off_t len; + int result; -#endif + if (fd < 0) + return fd; - if (*(path + offset) == '/') - return offset; + len = git_futils_filesize(fd); + if (!git__is_sizet(len)) { + giterr_set(GITERR_OS, "File `%s` too large to mmap", path); + return -1; + } - return -1; /* Not a real error. Rather a signal than the path is not rooted */ + result = git_futils_mmap_ro(out, fd, 0, (size_t)len); + p_close(fd); + return result; } +void git_futils_mmap_free(git_map *out) +{ + p_munmap(out); +} -int gitfo_mkdir_recurs(const char *path, int mode) +int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode) { - int error, root_path_offset; + git_buf make_path = GIT_BUF_INIT; + size_t start = 0; char *pp, *sp; - char *path_copy = git__strdup(path); + bool failed = false; + + if (base != NULL) { + /* + * when a base is being provided, it is supposed to already exist. + * Therefore, no attempt is being made to recursively create this leading path + * segment. It's just skipped. */ + start = strlen(base); + if (git_buf_joinpath(&make_path, base, path) < 0) + return -1; + } else { + int root_path_offset; - if (path_copy == NULL) - return GIT_ENOMEM; + if (git_buf_puts(&make_path, path) < 0) + return -1; - error = GIT_SUCCESS; - pp = path_copy; + root_path_offset = git_path_root(make_path.ptr); + if (root_path_offset > 0) { + /* + * On Windows, will skip the drive name (eg. C: or D:) + * or the leading part of a network path (eg. //computer_name ) */ + start = root_path_offset; + } + } - root_path_offset = retrieve_path_root_offset(pp); - if (root_path_offset > 0) - pp += root_path_offset; /* On Windows, will skip the drive name (eg. C: or D:) */ + pp = make_path.ptr + start; - while (error == GIT_SUCCESS && (sp = strchr(pp, '/')) != 0) { - if (sp != pp && gitfo_isdir(path_copy) < GIT_SUCCESS) { + while (!failed && (sp = strchr(pp, '/')) != NULL) { + if (sp != pp && git_path_isdir(make_path.ptr) == false) { *sp = 0; - error = gitfo_mkdir(path_copy, 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 = '/'; } @@ -352,201 +285,198 @@ int gitfo_mkdir_recurs(const char *path, int mode) pp = sp + 1; } - if (*(pp - 1) != '/' && error == GIT_SUCCESS) - error = gitfo_mkdir(path, mode); + if (*pp != '\0' && !failed) { + if (p_mkdir(make_path.ptr, mode) < 0 && errno != EEXIST) + failed = true; + } - free(path_copy); + 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 retrieve_previous_path_component_start(const char *path) +static int _rmdir_recurs_foreach(void *opaque, git_buf *path) { - int offset, len, root_offset, start = 0; + 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); - root_offset = retrieve_path_root_offset(path); - if (root_offset > -1) - start += root_offset; + if (git_path_isdir(path->ptr) == true) { + if (git_path_direach(path, _rmdir_recurs_foreach, opaque) < 0) + return -1; - len = strlen(path); - offset = len - 1; + if (p_rmdir(path->ptr) < 0) { + if (removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS && (errno == ENOTEMPTY || errno == EEXIST)) + return 0; + + giterr_set(GITERR_OS, "Could not remove directory '%s'", path->ptr); + return -1; + } - /* Skip leading slash */ - if (path[start] == '/') - start++; + return 0; + } - /* Skip trailing slash */ - if (path[offset] == '/') - offset--; + 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; + } - if (offset < root_offset) - return git__throw(GIT_ERROR, "Failed to retrieve path component. Wrong offset"); + return 0; + } - while (offset > start && path[offset-1] != '/') { - offset--; + if (removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY) { + giterr_set(GITERR_OS, "Could not remove directory. File '%s' still present", path->ptr); + return -1; } - return offset; + return 0; } -int gitfo_prettify_dir_path(char *buffer_out, size_t size, const char *path) +int git_futils_rmdir_r(const char *path, git_directory_removal_type removal_type) { - int len = 0, segment_len, only_dots, root_path_offset, error = GIT_SUCCESS; - char *current; - const char *buffer_out_start, *buffer_end; - - current = (char *)path; - buffer_end = path + strlen(path); - buffer_out_start = buffer_out; - - root_path_offset = retrieve_path_root_offset(path); - if (root_path_offset < 0) { - error = gitfo_getcwd(buffer_out, size); - if (error < GIT_SUCCESS) - return error; /* The callee already takes care of setting the correct error message. */ - - len = strlen(buffer_out); - buffer_out += len; - } + int error; + git_buf p = GIT_BUF_INIT; - while (current < buffer_end) { - /* Prevent multiple slashes from being added to the output */ - if (*current == '/' && len > 0 && buffer_out_start[len - 1] == '/') { - current++; - continue; - } - - only_dots = 1; - segment_len = 0; - - /* Copy path segment to the output */ - while (current < buffer_end && *current != '/') - { - only_dots &= (*current == '.'); - *buffer_out++ = *current++; - segment_len++; - len++; - } + error = git_buf_sets(&p, path); + if (!error) + error = _rmdir_recurs_foreach(&removal_type, &p); + git_buf_free(&p); + return error; +} - /* Skip current directory */ - if (only_dots && segment_len == 1) - { - current++; - buffer_out -= segment_len; - len -= segment_len; - continue; - } +#ifdef GIT_WIN32 +struct win32_path { + wchar_t path[MAX_PATH]; + DWORD len; +}; - /* Handle the double-dot upward directory navigation */ - if (only_dots && segment_len == 2) - { - current++; - buffer_out -= segment_len; +static int win32_expand_path(struct win32_path *s_root, const wchar_t *templ) +{ + s_root->len = ExpandEnvironmentStringsW(templ, s_root->path, MAX_PATH); + return s_root->len ? 0 : -1; +} - *buffer_out ='\0'; - len = retrieve_previous_path_component_start(buffer_out_start); +static int win32_find_file(git_buf *path, const struct win32_path *root, const char *filename) +{ + int error = 0; + size_t len; + wchar_t *file_utf16 = NULL; + char *file_utf8 = NULL; - /* Are we escaping out of the root dir? */ - if (len < 0) - return git__throw(GIT_EINVALIDPATH, "Failed to normalize path `%s`. The path escapes out of the root directory", path); + if (!root || !filename || (len = strlen(filename)) == 0) + return GIT_ENOTFOUND; - buffer_out = (char *)buffer_out_start + len; - continue; - } + /* allocate space for wchar_t path to file */ + file_utf16 = git__calloc(root->len + len + 2, sizeof(wchar_t)); + GITERR_CHECK_ALLOC(file_utf16); - /* Guard against potential multiple dot path traversal (cf http://cwe.mitre.org/data/definitions/33.html) */ - if (only_dots && segment_len > 0) - return git__throw(GIT_EINVALIDPATH, "Failed to normalize path `%s`. The path contains a segment with three `.` or more", path); + /* append root + '\\' + filename as wchar_t */ + memcpy(file_utf16, root->path, root->len * sizeof(wchar_t)); - *buffer_out++ = '/'; - len++; + if (*filename == '/' || *filename == '\\') + filename++; + + if (gitwin_append_utf16(file_utf16 + root->len - 1, filename, len + 1) != + (int)len + 1) { + error = -1; + goto cleanup; + } + + /* check access */ + if (_waccess(file_utf16, F_OK) < 0) { + error = GIT_ENOTFOUND; + goto cleanup; } - *buffer_out = '\0'; + /* convert to utf8 */ + if ((file_utf8 = gitwin_from_utf16(file_utf16)) == NULL) + error = -1; + else { + git_path_mkposix(file_utf8); + git_buf_attach(path, file_utf8, 0); + } - return GIT_SUCCESS; +cleanup: + git__free(file_utf16); + return error; } +#endif -int gitfo_prettify_file_path(char *buffer_out, size_t size, const char *path) +int git_futils_find_system_file(git_buf *path, const char *filename) { - int error, path_len, i; - const char* pattern = "/.."; - - path_len = strlen(path); - - /* Let's make sure the filename isn't empty nor a dot */ - if (path_len == 0 || (path_len == 1 && *path == '.')) - return git__throw(GIT_EINVALIDPATH, "Failed to normalize file path `%s`. The path is either empty or equals `.`", path); +#ifdef GIT_WIN32 + struct win32_path root; - /* Let's make sure the filename doesn't end with "/", "/." or "/.." */ - for (i = 1; path_len > i && i < 4; i++) { - if (!strncmp(path + path_len - i, pattern, i)) - return git__throw(GIT_EINVALIDPATH, "Failed to normalize file path `%s`. The path points to a folder", path); + if (win32_expand_path(&root, L"%PROGRAMFILES%\\Git\\etc\\") < 0 || + root.path[0] == L'%') /* i.e. no expansion happened */ + { + giterr_set(GITERR_OS, "Cannot locate the system's Program Files directory"); + return -1; } - error = gitfo_prettify_dir_path(buffer_out, size, path); - if (error < GIT_SUCCESS) - return error; /* The callee already takes care of setting the correct error message. */ + if (win32_find_file(path, &root, filename) < 0) { + git_buf_clear(path); + return GIT_ENOTFOUND; + } - path_len = strlen(buffer_out); - if (path_len < 2) /* TODO: Fixme. We should also take of detecting Windows rooted path (probably through usage of retrieve_path_root_offset) */ - return git__throw(GIT_EINVALIDPATH, "Failed to normalize file path `%s`. The path points to a folder", path); + return 0; - /* Remove the trailing slash */ - buffer_out[path_len - 1] = '\0'; +#else + if (git_buf_joinpath(path, "/etc", filename) < 0) + return -1; - return GIT_SUCCESS; -} + if (git_path_exists(path->ptr) == true) + return 0; -int gitfo_cmp_path(const char *name1, int len1, int isdir1, - const char *name2, int len2, int isdir2) -{ - int 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] - '/')); - if (len1 > len2) - return ((!isdir1 && !isdir2) ? 1 : - (isdir2 ? name1[len2] - '/' : '/' - name1[len2])); - return 0; + git_buf_clear(path); + return GIT_ENOTFOUND; +#endif } -static void posixify_path(char *path) +int git_futils_find_global_file(git_buf *path, const char *filename) { - while (*path) { - if (*path == '\\') - *path = '/'; +#ifdef GIT_WIN32 + struct win32_path root; - path++; + if (win32_expand_path(&root, L"%USERPROFILE%\\") < 0 || + root.path[0] == L'%') /* i.e. no expansion happened */ + { + giterr_set(GITERR_OS, "Cannot locate the user's profile directory"); + return -1; } -} - -int gitfo_getcwd(char *buffer_out, size_t size) -{ - char *cwd_buffer; - assert(buffer_out && size > 0); + if (win32_find_file(path, &root, filename) < 0) { + git_buf_clear(path); + return GIT_ENOTFOUND; + } -#ifdef GIT_WIN32 - cwd_buffer = _getcwd(buffer_out, size); + return 0; #else - cwd_buffer = getcwd(buffer_out, size); -#endif + const char *home = getenv("HOME"); - if (cwd_buffer == NULL) - return git__throw(GIT_EOSERR, "Failed to retrieve current working directory"); + if (home == NULL) { + giterr_set(GITERR_OS, "Global file lookup failed. " + "Cannot locate the user's home directory"); + return -1; + } - posixify_path(buffer_out); + if (git_buf_joinpath(path, home, filename) < 0) + return -1; - git__joinpath(buffer_out, buffer_out, ""); //Ensure the path ends with a trailing slash + if (git_path_exists(path->ptr) == false) { + git_buf_clear(path); + return GIT_ENOTFOUND; + } - return GIT_SUCCESS; + return 0; +#endif } diff --git a/src/fileops.h b/src/fileops.h index d0381b675..b0c5779e5 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -1,94 +1,118 @@ /* - * fileops.h - OS agnostic disk io operations + * Copyright (C) 2009-2012 the libgit2 contributors * - * This header describes the strictly internal part of the api + * 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_fileops_h__ #define INCLUDE_fileops_h__ #include "common.h" #include "map.h" -#include "dir.h" -#include <fcntl.h> -#include <time.h> - -#ifdef GIT_WIN32 -GIT_INLINE(int) link(const char *GIT_UNUSED(old), const char *GIT_UNUSED(new)) -{ - GIT_UNUSED_ARG(old) - GIT_UNUSED_ARG(new) - errno = ENOSYS; - return -1; -} - -GIT_INLINE(int) git__mkdir(const char *path, int GIT_UNUSED(mode)) -{ - GIT_UNUSED_ARG(mode) - return mkdir(path); -} - -extern int git__unlink(const char *path); -extern int git__mkstemp(char *template); -extern int git__fsync(int fd); - -# ifndef GIT__WIN32_NO_HIDE_FILEOPS -# define unlink(p) git__unlink(p) -# define mkstemp(t) git__mkstemp(t) -# define mkdir(p,m) git__mkdir(p, m) -# define fsync(fd) git__fsync(fd) -# endif -#endif /* GIT_WIN32 */ - - -#if !defined(O_BINARY) -#define O_BINARY 0 -#endif - -#define GITFO_BUF_INIT {NULL, 0} - -typedef int git_file; - -typedef struct { /* file io buffer */ - void *data; /* data bytes */ - size_t len; /* data length */ -} gitfo_buf; - -extern int gitfo_exists(const char *path); -extern int gitfo_open(const char *path, int flags); -extern int gitfo_creat(const char *path, int mode); -extern int gitfo_creat_force(const char *path, int mode); -extern int gitfo_mktemp(char *path_out, const char *filename); -extern int gitfo_isdir(const char *path); -extern int gitfo_mkdir_recurs(const char *path, int mode); -extern int gitfo_mkdir_2file(const char *path); -#define gitfo_close(fd) close(fd) - -extern int gitfo_read(git_file fd, void *buf, size_t cnt); -extern int gitfo_write(git_file fd, void *buf, size_t cnt); -#define gitfo_lseek(f,n,w) lseek(f, n, w) -extern git_off_t gitfo_size(git_file fd); - -extern int gitfo_read_file(gitfo_buf *obj, const char *path); -extern void gitfo_free_buf(gitfo_buf *obj); - -/* Move (rename) a file; this operation is atomic */ -extern int gitfo_mv(const char *from, const char *to); - -/* Move a file (forced); this will create the destination - * path if it doesn't exist */ -extern int gitfo_mv_force(const char *from, const char *to); - -#define gitfo_stat(p,b) stat(p, b) -#define gitfo_fstat(f,b) fstat(f, b) - -#define gitfo_unlink(p) unlink(p) -#define gitfo_rmdir(p) rmdir(p) -#define gitfo_chdir(p) chdir(p) -#define gitfo_mkdir(p,m) mkdir(p, m) - -#define gitfo_mkstemp(t) mkstemp(t) -#define gitfo_fsync(fd) fsync(fd) -#define gitfo_chmod(p,m) chmod(p, m) +#include "posix.h" +#include "path.h" + +/** + * Filebuffer methods + * + * Read whole files into an in-memory buffer for processing + */ +extern int git_futils_readbuffer(git_buf *obj, const char *path); +extern int git_futils_readbuffer_updated(git_buf *obj, const char *path, time_t *mtime, int *updated); + +/** + * File utils + * + * These are custom filesystem-related helper methods. They are + * rather high level, and wrap the underlying POSIX methods + * + * All these methods return 0 on success, + * or an error code on failure and an error message is set. + */ + +/** + * Create and open a file, while also + * creating all the folders in its path + */ +extern int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode); + +/** + * Create an open a process-locked file + */ +extern int git_futils_creat_locked(const char *path, const mode_t mode); + +/** + * Create an open a process-locked file, while + * also creating all the folders in its path + */ +extern int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode); + +/** + * Create a path recursively + * + * If a base parameter is being passed, it's expected to be valued with a path pointing to an already + * exisiting directory. + */ +extern int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode); + +/** + * Create all the folders required to contain + * the full path of a file + */ +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, git_directory_removal_type removal_type); + +/** + * Create and open a temporary file with a `_git2_` suffix. + * Writes the filename into path_out. + * @return On success, an open file descriptor, else an error code < 0. + */ +extern int git_futils_mktmp(git_buf *path_out, const char *filename); + +/** + * Move a file on the filesystem, create the + * destination path if it doesn't exist + */ +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); + +#define GIT_MODE_PERMS_MASK 0777 +#define GIT_CANONICAL_PERMS(MODE) (((MODE) & 0100) ? 0755 : 0644) +#define GIT_MODE_TYPE(MODE) ((MODE) & ~GIT_MODE_PERMS_MASK) + +/** + * Convert a mode_t from the OS to a legal git mode_t value. + */ +extern mode_t git_futils_canonical_mode(mode_t raw_mode); + /** * Read-only map all or part of a file into memory. @@ -102,90 +126,57 @@ extern int gitfo_mv_force(const char *from, const char *to); * @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 gitfo_map_ro( +extern int git_futils_mmap_ro( git_map *out, git_file fd, git_off_t begin, size_t len); /** + * Read-only map an entire file. + * + * @param out buffer to populate with the mapping information. + * @param path path to file to be opened. + * @return + * - 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, + const char *path); + +/** * Release the memory associated with a previous memory mapping. * @param map the mapping description previously configured. */ -extern void gitfo_free_map(git_map *map); +extern void git_futils_mmap_free(git_map *map); /** - * Walk each directory entry, except '.' and '..', calling fn(state). - * - * @param pathbuf buffer the function reads the initial directory - * path from, and updates with each successive entry's name. - * @param pathmax maximum allocation of pathbuf. - * @param fn function to invoke with each entry. The first arg is - * the input state and the second arg is pathbuf. The function - * may modify the pathbuf, but only by appending new text. - * @param state to pass to fn as the first arg. - */ -extern int gitfo_dirent( - char *pathbuf, - size_t pathmax, - int (*fn)(void *, char *), - void *state); - -extern int gitfo_cmp_path(const char *name1, int len1, int isdir1, - const char *name2, int len2, int isdir2); - -extern int gitfo_getcwd(char *buffer_out, size_t size); - -/** - * Clean up a provided absolute or relative directory path. - * - * This prettification relies on basic operations such as coalescing - * multiple forward slashes into a single slash, removing '.' and - * './' current directory segments, and removing parent directory - * whenever '..' is encountered. - * - * If not empty, the returned path ends with a forward slash. - * - * For instance, this will turn "d1/s1///s2/..//../s3" into "d1/s3/". + * Find a "global" file (i.e. one in a user's home directory). * - * This only performs a string based analysis of the path. - * No checks are done to make sure the path actually makes sense from - * the file system perspective. - * - * @param buffer_out buffer to populate with the normalized path. - * @param size buffer size. - * @param path directory path to clean up. + * @param pathbuf buffer to write the full path into + * @param filename name of file to find in the home directory * @return - * - GIT_SUCCESS on success; - * - GIT_ERROR when the input path is invalid or escapes the current directory. + * - 0 if found; + * - GIT_ENOTFOUND if not found; + * - -1 on an unspecified OS related error. */ -int gitfo_prettify_dir_path(char *buffer_out, size_t size, const char *path); +extern int git_futils_find_global_file(git_buf *path, const char *filename); /** - * Clean up a provided absolute or relative file path. - * - * This prettification relies on basic operations such as coalescing - * multiple forward slashes into a single slash, removing '.' and - * './' current directory segments, and removing parent directory - * whenever '..' is encountered. - * - * For instance, this will turn "d1/s1///s2/..//../s3" into "d1/s3". - * - * This only performs a string based analysis of the path. - * No checks are done to make sure the path actually makes sense from - * the file system perspective. + * Find a "system" file (i.e. one shared for all users of the system). * - * @param buffer_out buffer to populate with the normalized path. - * @param size buffer size. - * @param path file path to clean up. + * @param pathbuf buffer to write the full path into + * @param filename name of file to find in the home directory * @return - * - GIT_SUCCESS on success; - * - GIT_ERROR when the input path is invalid or escapes the current directory. + * - 0 if found; + * - GIT_ENOTFOUND if not found; + * - -1 on an unspecified OS related error. */ -int gitfo_prettify_file_path(char *buffer_out, size_t size, const char *path); +extern int git_futils_find_system_file(git_buf *path, const char *filename); -int retrieve_path_root_offset(const char *path); #endif /* INCLUDE_fileops_h__ */ diff --git a/src/filter.c b/src/filter.c new file mode 100644 index 000000000..8fa3eb684 --- /dev/null +++ b/src/filter.c @@ -0,0 +1,165 @@ +/* + * 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 "fileops.h" +#include "hash.h" +#include "filter.h" +#include "repository.h" +#include "git2/config.h" + +/* Tweaked from Core Git. I wonder what we could use this for... */ +void git_text_gather_stats(git_text_stats *stats, const git_buf *text) +{ + size_t i; + + memset(stats, 0, sizeof(*stats)); + + for (i = 0; i < git_buf_len(text); i++) { + unsigned char c = text->ptr[i]; + + if (c == '\r') { + stats->cr++; + + if (i + 1 < git_buf_len(text) && text->ptr[i + 1] == '\n') + stats->crlf++; + } + + else if (c == '\n') + stats->lf++; + + else if (c == 0x85) + /* Unicode CR+LF */ + stats->crlf++; + + else if (c == 127) + /* DEL */ + stats->nonprintable++; + + else if (c <= 0x1F || (c >= 0x80 && c <= 0x9F)) { + switch (c) { + /* BS, HT, ESC and FF */ + case '\b': case '\t': case '\033': case '\014': + stats->printable++; + break; + case 0: + stats->nul++; + /* fall through */ + default: + stats->nonprintable++; + } + } + + else + stats->printable++; + } + + /* If file ends with EOF then don't count this EOF as non-printable. */ + if (git_buf_len(text) >= 1 && text->ptr[text->size - 1] == '\032') + stats->nonprintable--; +} + +/* + * Fresh from Core Git + */ +int git_text_is_binary(git_text_stats *stats) +{ + if (stats->nul) + return 1; + + if ((stats->printable >> 7) < stats->nonprintable) + return 1; + /* + * Other heuristics? Average line length might be relevant, + * as might LF vs CR vs CRLF counts.. + * + * NOTE! It might be normal to have a low ratio of CRLF to LF + * (somebody starts with a LF-only file and edits it with an editor + * that adds CRLF only to lines that are added..). But do we + * want to support CR-only? Probably not. + */ + return 0; +} + +int git_filters_load(git_vector *filters, git_repository *repo, const char *path, int mode) +{ + int error; + + if (mode == GIT_FILTER_TO_ODB) { + /* Load the CRLF cleanup filter when writing to the ODB */ + error = git_filter_add__crlf_to_odb(filters, repo, path); + if (error < 0) + return error; + } else { + giterr_set(GITERR_INVALID, "Worktree filters are not implemented yet"); + return -1; + } + + return (int)filters->length; +} + +void git_filters_free(git_vector *filters) +{ + size_t i; + git_filter *filter; + + git_vector_foreach(filters, i, filter) { + if (filter->do_free != NULL) + filter->do_free(filter); + else + git__free(filter); + } + + git_vector_free(filters); +} + +int git_filters_apply(git_buf *dest, git_buf *source, git_vector *filters) +{ + unsigned int i, src; + git_buf *dbuffer[2]; + + dbuffer[0] = source; + dbuffer[1] = dest; + + src = 0; + + if (git_buf_len(source) == 0) { + git_buf_clear(dest); + return 0; + } + + /* Pre-grow the destination buffer to more or less the size + * we expect it to have */ + if (git_buf_grow(dest, git_buf_len(source)) < 0) + return -1; + + for (i = 0; i < filters->length; ++i) { + git_filter *filter = git_vector_get(filters, i); + unsigned int dst = 1 - src; + + git_buf_clear(dbuffer[dst]); + + /* Apply the filter from dbuffer[src] to the other buffer; + * if the filtering is canceled by the user mid-filter, + * we skip to the next filter without changing the source + * of the double buffering (so that the text goes through + * cleanly). + */ + if (filter->apply(filter, dbuffer[dst], dbuffer[src]) == 0) + src = dst; + + if (git_buf_oom(dbuffer[dst])) + return -1; + } + + /* Ensure that the output ends up in dbuffer[1] (i.e. the dest) */ + if (src != 1) + git_buf_swap(dest, source); + + return 0; +} + diff --git a/src/filter.h b/src/filter.h new file mode 100644 index 000000000..66e370aef --- /dev/null +++ b/src/filter.h @@ -0,0 +1,119 @@ +/* + * 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_filter_h__ +#define INCLUDE_filter_h__ + +#include "common.h" +#include "buffer.h" +#include "git2/odb.h" +#include "git2/repository.h" + +typedef struct git_filter { + int (*apply)(struct git_filter *self, git_buf *dest, const git_buf *source); + void (*do_free)(struct git_filter *self); +} git_filter; + +typedef enum { + GIT_FILTER_TO_WORKTREE, + GIT_FILTER_TO_ODB +} git_filter_mode; + +typedef enum { + GIT_CRLF_GUESS = -1, + GIT_CRLF_BINARY = 0, + GIT_CRLF_TEXT, + GIT_CRLF_INPUT, + GIT_CRLF_CRLF, + GIT_CRLF_AUTO, +} git_crlf_t; + +typedef struct { + /* NUL, CR, LF and CRLF counts */ + unsigned int nul, cr, lf, crlf; + + /* These are just approximations! */ + unsigned int printable, nonprintable; +} git_text_stats; + +/* + * FILTER API + */ + +/* + * For any given path in the working directory, fill the `filters` + * array with the relevant filters that need to be applied. + * + * Mode is either `GIT_FILTER_TO_WORKTREE` if you need to load the + * filters that will be used when checking out a file to the working + * directory, or `GIT_FILTER_TO_ODB` for the filters used when writing + * a file to the ODB. + * + * @param filters Vector where to store all the loaded filters + * @param repo Repository object that contains `path` + * @param path Relative path of the file to be filtered + * @param mode Filtering direction (WT->ODB or ODB->WT) + * @return the number of filters loaded for the file (0 if the file + * doesn't need filtering), or a negative error code + */ +extern int git_filters_load(git_vector *filters, git_repository *repo, const char *path, int mode); + +/* + * Apply one or more filters to a file. + * + * The file must have been loaded as a `git_buf` object. Both the `source` + * and `dest` buffers are owned by the caller and must be freed once + * they are no longer needed. + * + * NOTE: Because of the double-buffering schema, the `source` buffer that contains + * the original file may be tampered once the filtering is complete. Regardless, + * the `dest` buffer will always contain the final result of the filtering + * + * @param dest Buffer to store the result of the filtering + * @param source Buffer containing the document to filter + * @param filters A non-empty vector of filters as supplied by `git_filters_load` + * @return 0 on success, an error code otherwise + */ +extern int git_filters_apply(git_buf *dest, git_buf *source, git_vector *filters); + +/* + * Free the `filters` array generated by `git_filters_load`. + * + * Note that this frees both the array and its contents. The array will + * be clean/reusable after this call. + * + * @param filters A filters array as supplied by `git_filters_load` + */ +extern void git_filters_free(git_vector *filters); + +/* + * Available filters + */ + +/* Strip CRLF, from Worktree to ODB */ +extern int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const char *path); + + +/* + * PLAINTEXT API + */ + +/* + * Gather stats for a piece of text + * + * Fill the `stats` structure with information on the number of + * unreadable characters, carriage returns, etc, so it can be + * used in heuristics. + */ +extern void git_text_gather_stats(git_text_stats *stats, const git_buf *text); + +/* + * Process `git_text_stats` data generated by `git_text_stat` to see + * if it qualifies as a binary file + */ +extern int git_text_is_binary(git_text_stats *stats); + +#endif diff --git a/src/global.c b/src/global.c new file mode 100644 index 000000000..368c6c664 --- /dev/null +++ b/src/global.c @@ -0,0 +1,134 @@ +/* + * 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 "global.h" +#include "git2/threads.h" +#include "thread-utils.h" + +/** + * Handle the global state with TLS + * + * If libgit2 is built with GIT_THREADS enabled, + * the `git_threads_init()` function must be called + * before calling any other function of the library. + * + * This function allocates a TLS index (using pthreads + * or the native Win32 API) to store the global state + * on a per-thread basis. + * + * Any internal method that requires global state will + * then call `git__global_state()` which returns a pointer + * to the global state structure; this pointer is lazily + * allocated on each thread. + * + * Before shutting down the library, the + * `git_threads_shutdown` method must be called to free + * the previously reserved TLS index. + * + * If libgit2 is built without threading support, the + * `git__global_statestate()` call returns a pointer to a single, + * statically allocated global state. The `git_thread_` + * functions are not available in that case. + */ + +#if defined(GIT_THREADS) && defined(GIT_WIN32) + +static DWORD _tls_index; +static int _tls_init = 0; + +void git_threads_init(void) +{ + if (_tls_init) + return; + + _tls_index = TlsAlloc(); + _tls_init = 1; +} + +void git_threads_shutdown(void) +{ + TlsFree(_tls_index); + _tls_init = 0; +} + +git_global_st *git__global_state(void) +{ + void *ptr; + + if ((ptr = TlsGetValue(_tls_index)) != NULL) + return ptr; + + ptr = git__malloc(sizeof(git_global_st)); + if (!ptr) + return NULL; + + memset(ptr, 0x0, sizeof(git_global_st)); + TlsSetValue(_tls_index, ptr); + return ptr; +} + +#elif defined(GIT_THREADS) && defined(_POSIX_THREADS) + +static pthread_key_t _tls_key; +static int _tls_init = 0; + +static void cb__free_status(void *st) +{ + git__free(st); +} + +void git_threads_init(void) +{ + if (_tls_init) + return; + + pthread_key_create(&_tls_key, &cb__free_status); + _tls_init = 1; +} + +void git_threads_shutdown(void) +{ + pthread_key_delete(_tls_key); + _tls_init = 0; +} + +git_global_st *git__global_state(void) +{ + void *ptr; + + if ((ptr = pthread_getspecific(_tls_key)) != NULL) + return ptr; + + ptr = git__malloc(sizeof(git_global_st)); + if (!ptr) + return NULL; + + memset(ptr, 0x0, sizeof(git_global_st)); + pthread_setspecific(_tls_key, ptr); + return ptr; +} + +#else + +static git_global_st __state; + +void git_threads_init(void) +{ + /* noop */ +} + +void git_threads_shutdown(void) +{ + /* noop */ +} + +git_global_st *git__global_state(void) +{ + return &__state; +} + +#endif /* GIT_THREADS */ diff --git a/src/global.h b/src/global.h new file mode 100644 index 000000000..2b525ce07 --- /dev/null +++ b/src/global.h @@ -0,0 +1,27 @@ +/* + * 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_global_h__ +#define INCLUDE_global_h__ + +#include "mwindow.h" + +typedef struct { + struct { + char last[1024]; + } error; + + git_error *last_error; + git_error error_t; + + git_mwindow_ctl mem_ctl; +} git_global_st; + +git_global_st *git__global_state(void); + +#define GIT_GLOBAL (git__global_state()) + +#endif diff --git a/src/hash.c b/src/hash.c index 775e4b4c1..460756913 100644 --- a/src/hash.c +++ b/src/hash.c @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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" @@ -28,10 +10,8 @@ #if defined(PPC_SHA1) # include "ppc/sha1.h" -#elif defined(OPENSSL_SHA1) -# include <openssl/sha.h> #else -# include "block-sha1/sha1.h" +# include "sha1.h" #endif struct git_hash_ctx { @@ -52,7 +32,7 @@ git_hash_ctx *git_hash_new_ctx(void) void git_hash_free_ctx(git_hash_ctx *ctx) { - free(ctx); + git__free(ctx); } void git_hash_init(git_hash_ctx *ctx) diff --git a/src/hash.h b/src/hash.h index 2b769a4c9..33d7b20cd 100644 --- a/src/hash.h +++ b/src/hash.h @@ -1,5 +1,8 @@ /* - * hash.h + * 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_hash_h__ #define INCLUDE_hash_h__ diff --git a/src/hashtable.c b/src/hashtable.c deleted file mode 100644 index b40737d67..000000000 --- a/src/hashtable.c +++ /dev/null @@ -1,261 +0,0 @@ -/* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#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; - free(self->nodes); - } - } while(self->is_resizing); - - free(old_nodes); - return GIT_SUCCESS; -} - -static int set_size(git_hashtable *self, size_t new_size) -{ - self->nodes = 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); - - free(self->nodes); - 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_remove(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) { - 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); -} - diff --git a/src/hashtable.h b/src/hashtable.h deleted file mode 100644 index c3475b6ed..000000000 --- a/src/hashtable.h +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef INCLUDE_hashtable_h__ -#define INCLUDE_hashtable_h__ - -#include "git2/common.h" -#include "git2/oid.h" -#include "git2/odb.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_remove(git_hashtable *table, const void *key); -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, pkey, pvalue, code) {\ - git_hashtable *_self = (self);\ - git_hashtable_node *_nodes = _self->nodes;\ - unsigned int _i, _size = _self->size;\ - for (_i = 0; _i < _size; _i ++) {\ - git_hashtable_node *_node = git_hashtable_node_at(_nodes, _i);\ - if (_node->key)\ - {\ - pkey = _node->key;\ - pvalue = _node->value;\ - code;\ - }\ - }\ -} - -#define GIT_HASHTABLE_FOREACH_DELETE() {\ - _node->key = NULL; _node->value = NULL; _self->key_count--;\ -} - - -#endif diff --git a/src/ignore.c b/src/ignore.c new file mode 100644 index 000000000..fc6194bb5 --- /dev/null +++ b/src/ignore.c @@ -0,0 +1,203 @@ +#include "ignore.h" +#include "path.h" + +#define GIT_IGNORE_INTERNAL "[internal]exclude" +#define GIT_IGNORE_FILE_INREPO "info/exclude" +#define GIT_IGNORE_FILE ".gitignore" + +static int parse_ignore_file( + git_repository *repo, const char *buffer, git_attr_file *ignores) +{ + int error = 0; + git_attr_fnmatch *match = NULL; + const char *scan = NULL; + char *context = NULL; + + GIT_UNUSED(repo); + + if (ignores->key && git__suffixcmp(ignores->key, "/" GIT_IGNORE_FILE) == 0) { + context = ignores->key + 2; + context[strlen(context) - strlen(GIT_IGNORE_FILE)] = '\0'; + } + + scan = buffer; + + while (!error && *scan) { + if (!match) { + match = git__calloc(1, sizeof(*match)); + GITERR_CHECK_ALLOC(match); + } + + 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 != 0) { + git__free(match->pattern); + match->pattern = NULL; + + if (error == GIT_ENOTFOUND) + error = 0; + } else { + match = NULL; /* vector now "owns" the match */ + } + } + + git__free(match); + /* restore file path used for context */ + if (context) + context[strlen(context)] = '.'; /* first char of GIT_IGNORE_FILE */ + + return error; +} + +#define push_ignore_file(R,S,B,F) \ + git_attr_cache__push_file((R),(B),(F),GIT_ATTR_FILE_FROM_FILE,parse_ignore_file,(S)) + +static int push_one_ignore(void *ref, git_buf *path) +{ + git_ignores *ign = (git_ignores *)ref; + return push_ignore_file(ign->repo, &ign->ign_path, path->ptr, GIT_IGNORE_FILE); +} + +int git_ignore__for_path( + git_repository *repo, + const char *path, + git_ignores *ignores) +{ + int error = 0; + const char *workdir = git_repository_workdir(repo); + + assert(ignores); + + ignores->repo = repo; + git_buf_init(&ignores->dir, 0); + ignores->ign_internal = NULL; + + 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; + + /* given a unrooted path in a non-bare repo, resolve it */ + if (workdir && git_path_root(path) < 0) + error = git_path_find_dir(&ignores->dir, path, workdir); + else + error = git_buf_sets(&ignores->dir, path); + if (error < 0) + goto cleanup; + + /* set up internals */ + error = git_attr_cache__internal_file( + repo, GIT_IGNORE_INTERNAL, &ignores->ign_internal); + if (error < 0) + goto cleanup; + + /* load .gitignore up the path */ + if (workdir != NULL) { + error = git_path_walk_up( + &ignores->dir, workdir, push_one_ignore, ignores); + if (error < 0) + goto cleanup; + } + + /* load .git/info/exclude */ + error = push_ignore_file(repo, &ignores->ign_global, + git_repository_path(repo), GIT_IGNORE_FILE_INREPO); + if (error < 0) + goto cleanup; + + /* load core.excludesfile */ + if (git_repository_attr_cache(repo)->cfg_excl_file != NULL) + error = push_ignore_file(repo, &ignores->ign_global, NULL, + git_repository_attr_cache(repo)->cfg_excl_file); + +cleanup: + if (error < 0) + git_ignore__free(ignores); + + return error; +} + +int git_ignore__push_dir(git_ignores *ign, const char *dir) +{ + if (git_buf_joinpath(&ign->dir, ign->dir.ptr, dir) < 0) + return -1; + else + return push_ignore_file( + ign->repo, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE); +} + +int git_ignore__pop_dir(git_ignores *ign) +{ + if (ign->ign_path.length > 0) { + git_attr_file *file = git_vector_last(&ign->ign_path); + if (git__suffixcmp(ign->dir.ptr, file->key + 2) == 0) + git_vector_pop(&ign->ign_path); + git_buf_rtruncate_at_char(&ign->dir, '/'); + } + return 0; +} + +void git_ignore__free(git_ignores *ignores) +{ + /* don't need to free ignores->ign_internal since it is in cache */ + git_vector_free(&ignores->ign_path); + git_vector_free(&ignores->ign_global); + git_buf_free(&ignores->dir); +} + +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)) { + *ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0); + return true; + } + } + + return false; +} + +int git_ignore__lookup( + git_ignores *ignores, const char *pathname, int *ignored) +{ + unsigned int i; + git_attr_file *file; + git_attr_path path; + + if (git_attr_path__init( + &path, pathname, git_repository_workdir(ignores->repo)) < 0) + return -1; + + /* 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) { + if (ignore_lookup_in_rules(&file->rules, &path, ignored)) + goto cleanup; + } + + /* last process global ignores */ + git_vector_foreach(&ignores->ign_global, i, file) { + if (ignore_lookup_in_rules(&file->rules, &path, ignored)) + goto cleanup; + } + + *ignored = 0; + +cleanup: + git_attr_path__free(&path); + return 0; +} diff --git a/src/ignore.h b/src/ignore.h new file mode 100644 index 000000000..809d2edbd --- /dev/null +++ b/src/ignore.h @@ -0,0 +1,38 @@ +/* + * 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_ignore_h__ +#define INCLUDE_ignore_h__ + +#include "repository.h" +#include "vector.h" + +/* The git_ignores structure maintains three sets of ignores: + * - internal ignores + * - per directory ignores + * - global ignores (at lower priority than the others) + * As you traverse from one directory to another, you can push and pop + * directories onto git_ignores list efficiently. + */ +typedef struct { + git_repository *repo; + git_buf dir; + git_attr_file *ign_internal; + git_vector ign_path; + git_vector ign_global; +} git_ignores; + +extern int git_ignore__for_path(git_repository *repo, const char *path, git_ignores *ign); + +extern int git_ignore__push_dir(git_ignores *ign, const char *dir); + +extern int git_ignore__pop_dir(git_ignores *ign); + +extern void git_ignore__free(git_ignores *ign); + +extern int git_ignore__lookup(git_ignores *ign, const char *path, int *ignored); + +#endif diff --git a/src/index.c b/src/index.c index c86d37a08..f1ae9a710 100644 --- a/src/index.c +++ b/src/index.c @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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 <stddef.h> @@ -28,6 +10,8 @@ #include "common.h" #include "repository.h" #include "index.h" +#include "tree.h" +#include "tree-cache.h" #include "hash.h" #include "git2/odb.h" #include "git2/blob.h" @@ -48,6 +32,8 @@ static const unsigned int INDEX_HEADER_SIG = 0x44495243; static const char INDEX_EXT_TREECACHE_SIG[] = {'T', 'R', 'E', 'E'}; static const char INDEX_EXT_UNMERGED_SIG[] = {'R', 'E', 'U', 'C'}; +#define INDEX_OWNER(idx) ((git_repository *)(GIT_REFCOUNT_OWNER(idx))) + struct index_header { uint32_t signature; uint32_t version; @@ -98,121 +84,100 @@ static size_t read_extension(git_index *index, const char *buffer, size_t buffer static size_t read_entry(git_index_entry *dest, const void *buffer, size_t buffer_size); static int read_header(struct index_header *dest, const void *buffer); -static int read_tree(git_index *index, const char *buffer, size_t buffer_size); -static int read_tree_internal(git_index_tree **, const char **, const char *, git_index_tree *); - static int parse_index(git_index *index, const char *buffer, size_t buffer_size); static int is_index_extended(git_index *index); -static void sort_index(git_index *index); static int write_index(git_index *index, git_filebuf *file); -int index_srch(const void *key, const void *array_member) +static void index_entry_free(git_index_entry *entry); + +static int index_srch(const void *key, const void *array_member) { - const char *filename = (const char *)key; - const git_index_entry *entry = *(const git_index_entry **)(array_member); + const git_index_entry *entry = array_member; - return strcmp(filename, entry->path); + return strcmp(key, entry->path); } -int index_cmp(const void *a, const void *b) +static int index_cmp(const void *a, const void *b) { - const git_index_entry *entry_a = *(const git_index_entry **)(a); - const git_index_entry *entry_b = *(const git_index_entry **)(b); + const git_index_entry *entry_a = a; + const git_index_entry *entry_b = b; return strcmp(entry_a->path, entry_b->path); } -int unmerged_srch(const void *key, const void *array_member) +static int unmerged_srch(const void *key, const void *array_member) { - const char *path = (const char *) key; - const git_index_entry_unmerged *entry = *(const git_index_entry_unmerged **) (array_member); + const git_index_entry_unmerged *entry = array_member; - return strcmp(path, entry->path); + return strcmp(key, entry->path); } -int unmerged_cmp(const void *a, const void *b) +static int unmerged_cmp(const void *a, const void *b) { - const git_index_entry_unmerged *info_a = *(const git_index_entry_unmerged **)(a); - const git_index_entry_unmerged *info_b = *(const git_index_entry_unmerged **)(b); + const git_index_entry_unmerged *info_a = a; + const git_index_entry_unmerged *info_b = b; return strcmp(info_a->path, info_b->path); } -static int index_initialize(git_index **index_out, git_repository *owner, const char *index_path) +static unsigned int index_create_mode(unsigned int mode) +{ + if (S_ISLNK(mode)) + return S_IFLNK; + if (S_ISDIR(mode) || (mode & S_IFMT) == (S_IFLNK | S_IFDIR)) + return (S_IFLNK | S_IFDIR); + return S_IFREG | ((mode & 0100) ? 0755 : 0644); +} + +int git_index_open(git_index **index_out, const char *index_path) { git_index *index; 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) { - free(index); - return GIT_ENOMEM; - } - - index->repository = owner; + 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 (gitfo_exists(index->index_file_path) == 0) + if (git_path_exists(index->index_file_path) == true) index->on_disk = 1; *index_out = index; + GIT_REFCOUNT_INC(index); return git_index_read(index); } -int git_index_open(git_index **index_out, const char *index_path) -{ - assert(index_out && index_path); - return index_initialize(index_out, NULL, index_path); -} - -/* - * Moved from `repository.c` - */ -int git_repository_index(git_index **index_out, git_repository *repo) +static void index_free(git_index *index) { - assert(index_out && repo); - - if (repo->is_bare) - return git__throw(GIT_EBAREINDEX, "Failed to open index. Repository is bare"); - - return index_initialize(index_out, repo, repo->path_index); -} - -void git_index_free(git_index *index) -{ - if (index == NULL) - return; + git_index_entry *e; + unsigned int i; git_index_clear(index); + git_vector_foreach(&index->entries, i, e) { + index_entry_free(e); + } git_vector_free(&index->entries); + git_vector_foreach(&index->unmerged, i, e) { + index_entry_free(e); + } git_vector_free(&index->unmerged); - free(index->index_file_path); - free(index); + git__free(index->index_file_path); + git__free(index); } -static void free_tree(git_index_tree *tree) +void git_index_free(git_index *index) { - unsigned int i; - - if (tree == NULL) + if (index == NULL) return; - for (i = 0; i < tree->children_count; ++i) - free_tree(tree->children[i]); - - free(tree->name); - free(tree->children); - free(tree); + GIT_REFCOUNT_DEC(index, index_free); } void git_index_clear(git_index *index) @@ -224,90 +189,85 @@ void git_index_clear(git_index *index) for (i = 0; i < index->entries.length; ++i) { git_index_entry *e; e = git_vector_get(&index->entries, i); - free((char *)e->path); - free(e); + git__free(e->path); + git__free(e); } for (i = 0; i < index->unmerged.length; ++i) { git_index_entry_unmerged *e; e = git_vector_get(&index->unmerged, i); - free((char *)e->path); - free(e); + git__free(e->path); + git__free(e); } git_vector_clear(&index->entries); git_vector_clear(&index->unmerged); index->last_modified = 0; - free_tree(index->tree); + git_tree_cache_free(index->tree); index->tree = NULL; } int git_index_read(git_index *index) { - struct stat indexst; - int error = GIT_SUCCESS; + int error, updated; + git_buf buffer = GIT_BUF_INIT; + time_t mtime; assert(index->index_file_path); - if (!index->on_disk || gitfo_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; } - if (gitfo_stat(index->index_file_path, &indexst) < 0) - return git__throw(GIT_EOSERR, "Failed to read index. %s does not exist or is corrupted", index->index_file_path); - - if (!S_ISREG(indexst.st_mode)) - return git__throw(GIT_ENOTFOUND, "Failed to read index. %s is not an index file", index->index_file_path); - - if (indexst.st_mtime != index->last_modified) { - - gitfo_buf buffer; - - if ((error = gitfo_read_file(&buffer, index->index_file_path)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to read index"); + /* 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 < 0) + return error; + if (updated) { git_index_clear(index); - error = parse_index(index, buffer.data, buffer.len); + error = parse_index(index, buffer.ptr, buffer.size); - if (error == GIT_SUCCESS) - index->last_modified = indexst.st_mtime; + if (!error) + index->last_modified = mtime; - gitfo_free_buf(&buffer); + git_buf_free(&buffer); } - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to read index"); return error; } int git_index_write(git_index *index) { - git_filebuf file; + git_filebuf file = GIT_FILEBUF_INIT; struct stat indexst; int error; - sort_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_SUCCESS) - return git__rethrow(error, "Failed to write index"); + if ((error = git_filebuf_commit(&file, GIT_INDEX_FILE_MODE)) < 0) + return error; - if (gitfo_stat(index->index_file_path, &indexst) == 0) { + 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) @@ -322,39 +282,110 @@ unsigned int git_index_entrycount_unmerged(git_index *index) return index->unmerged.length; } -git_index_entry *git_index_get(git_index *index, int n) +git_index_entry *git_index_get(git_index *index, unsigned int n) { - assert(index); - sort_index(index); - return git_vector_get(&index->entries, (unsigned int)n); + git_vector_sort(&index->entries); + return git_vector_get(&index->entries, n); } -static void sort_index(git_index *index) +void git_index__init_entry_from_stat(struct stat *st, git_index_entry *entry) { - git_vector_sort(&index->entries); + 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_insert(git_index *index, const git_index_entry *source_entry, int replace) +static int index_entry_init(git_index_entry **entry_out, git_index *index, const char *rel_path, int stage) { - git_index_entry *entry; - size_t path_length; - int position; + git_index_entry *entry = NULL; + struct stat st; + git_oid oid; + const char *workdir; + git_buf full_path = GIT_BUF_INIT; + int error; + + assert(stage >= 0 && stage <= 3); + + 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; + } + + if ((error = git_buf_joinpath(&full_path, workdir, rel_path)) < 0) + return error; + + if ((error = git_path_lstat(full_path.ptr, &st)) < 0) { + git_buf_free(&full_path); + return error; + } + + git_buf_free(&full_path); /* done with full path */ + + /* There is no need to validate the rel_path here, since it will be + * immediately validated by the call to git_blob_create_fromfile. + */ + + /* write the blob to disk and get the oid */ + if ((error = git_blob_create_fromfile(&oid, INDEX_OWNER(index), rel_path)) < 0) + return error; + + entry = git__calloc(1, sizeof(git_index_entry)); + GITERR_CHECK_ALLOC(entry); + + git_index__init_entry_from_stat(&st, entry); - assert(index && source_entry); + entry->oid = oid; + entry->flags |= (stage << GIT_IDXENTRY_STAGESHIFT); + entry->path = git__strdup(rel_path); + GITERR_CHECK_ALLOC(entry->path); + + *entry_out = entry; + return 0; +} - if (source_entry->path == NULL) - return git__throw(GIT_EMISSINGOBJDATA, "Failed to insert into index. Entry has no path"); +static git_index_entry *index_entry_dup(const git_index_entry *source_entry) +{ + git_index_entry *entry; entry = git__malloc(sizeof(git_index_entry)); - if (entry == NULL) - return GIT_ENOMEM; + if (!entry) + return NULL; memcpy(entry, source_entry, sizeof(git_index_entry)); /* duplicate the path string so we own it */ entry->path = git__strdup(entry->path); - if (entry->path == NULL) - return GIT_ENOMEM; + if (!entry->path) + return NULL; + + return entry; +} + +static void index_entry_free(git_index_entry *entry) +{ + if (!entry) + return; + git__free(entry->path); + git__free(entry); +} + +static int index_insert(git_index *index, git_index_entry *entry, int replace) +{ + size_t path_length; + int position; + git_index_entry **entry_array; + + assert(index && entry && entry->path != NULL); /* make sure that the path length flag is correct */ path_length = strlen(entry->path); @@ -366,120 +397,127 @@ static int index_insert(git_index *index, const git_index_entry *source_entry, i else entry->flags |= GIT_IDXENTRY_NAMEMASK;; + /* + * replacing is not requested: just insert entry at the end; + * the index is no longer sorted + */ + if (!replace) + return git_vector_insert(&index->entries, entry); /* look if an entry with this path already exists */ - position = git_index_find(index, source_entry->path); + position = git_index_find(index, entry->path); - /* if no entry exists and replace is not set, - * add the entry at the end; - * the index is no longer sorted */ - if (!replace || position == GIT_ENOTFOUND) { - if (git_vector_insert(&index->entries, entry) < GIT_SUCCESS) - return GIT_ENOMEM; + /* + * if no entry exists add the entry at the end; + * the index is no longer sorted + */ + if (position == GIT_ENOTFOUND) + return git_vector_insert(&index->entries, entry); - /* if a previous entry exists and replace is set, - * replace it */ - } else { - git_index_entry **entry_array = (git_index_entry **)index->entries.contents; + /* exists, replace it */ + entry_array = (git_index_entry **) index->entries.contents; + git__free(entry_array[position]->path); + git__free(entry_array[position]); + entry_array[position] = entry; + + return 0; +} - free((char *)entry_array[position]->path); - free(entry_array[position]); +static int index_add(git_index *index, const char *path, int stage, int replace) +{ + git_index_entry *entry = NULL; + int ret; - entry_array[position] = entry; + if ((ret = index_entry_init(&entry, index, path, stage)) < 0 || + (ret = index_insert(index, entry, replace)) < 0) + { + index_entry_free(entry); + return ret; } - return GIT_SUCCESS; + git_tree_cache_invalidate_path(index->tree, entry->path); + return 0; } -static int index_init_entry(git_index_entry *entry, git_index *index, const char *rel_path, int stage) +int git_index_add(git_index *index, const char *path, int stage) { - char full_path[GIT_PATH_MAX]; - struct stat st; - int error; - - if (index->repository == NULL) - return git__throw(GIT_EBAREINDEX, "Failed to initialize entry. Repository is bare"); - - git__joinpath(full_path, index->repository->path_workdir, rel_path); + return index_add(index, path, stage, 1); +} - if (gitfo_exists(full_path) < 0) - return git__throw(GIT_ENOTFOUND, "Failed to initialize entry. %s does not exist", full_path); +int git_index_append(git_index *index, const char *path, int stage) +{ + return index_add(index, path, stage, 0); +} - if (gitfo_stat(full_path, &st) < 0) - return git__throw(GIT_EOSERR, "Failed to initialize entry. %s appears to be corrupted", full_path); +static int index_add2( + git_index *index, const git_index_entry *source_entry, int replace) +{ + git_index_entry *entry = NULL; + int ret; - if (stage < 0 || stage > 3) - return git__throw(GIT_ERROR, "Failed to initialize entry. Invalid stage %i", stage); + entry = index_entry_dup(source_entry); + if (entry == NULL) + return -1; - memset(entry, 0x0, sizeof(git_index_entry)); + if ((ret = index_insert(index, entry, replace)) < 0) { + index_entry_free(entry); + return ret; + } - 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 = st.st_mode; - entry->uid = st.st_uid; - entry->gid = st.st_gid; - entry->file_size = st.st_size; + git_tree_cache_invalidate_path(index->tree, entry->path); + return 0; +} - /* write the blob to disk and get the oid */ - if ((error = git_blob_create_fromfile(&entry->oid, index->repository, rel_path)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to initialize index entry"); +int git_index_add2(git_index *index, const git_index_entry *source_entry) +{ + return index_add2(index, source_entry, 1); +} - entry->flags |= (stage << GIT_IDXENTRY_STAGESHIFT); - entry->path = (char *)rel_path; /* do not duplicate; index_insert already does this */ - return GIT_SUCCESS; +int git_index_append2(git_index *index, const git_index_entry *source_entry) +{ + return index_add2(index, source_entry, 1); } -int git_index_add(git_index *index, const char *path, int stage) +int git_index_remove(git_index *index, int position) { int error; - git_index_entry entry; + git_index_entry *entry; - if ((error = index_init_entry(&entry, index, path, stage)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to add to index"); + git_vector_sort(&index->entries); - return index_insert(index, &entry, 1); -} + entry = git_vector_get(&index->entries, position); + if (entry != NULL) + git_tree_cache_invalidate_path(index->tree, entry->path); -int git_index_append(git_index *index, const char *path, int stage) -{ - int error; - git_index_entry entry; + error = git_vector_remove(&index->entries, (unsigned int)position); - if ((error = index_init_entry(&entry, index, path, stage)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to append to index"); + if (!error) + index_entry_free(entry); - return index_insert(index, &entry, 0); + return error; } -int git_index_add2(git_index *index, const git_index_entry *source_entry) +int git_index_find(git_index *index, const char *path) { - return index_insert(index, source_entry, 1); + return git_vector_bsearch2(&index->entries, index_srch, path); } -int git_index_append2(git_index *index, const git_index_entry *source_entry) +unsigned int git_index__prefix_position(git_index *index, const char *path) { - return index_insert(index, source_entry, 0); -} + unsigned int pos; + git_vector_bsearch3(&pos, &index->entries, index_srch, path); -int git_index_remove(git_index *index, int position) -{ - assert(index); - sort_index(index); - return git_vector_remove(&index->entries, (unsigned int)position); + return pos; } -int git_index_find(git_index *index, const char *path) +void git_index_uniq(git_index *index) { - sort_index(index); - return git_vector_bsearch2(&index->entries, index_srch, path); + git_vector_uniq(&index->entries); } -const git_index_entry_unmerged *git_index_get_unmerged(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); @@ -487,129 +525,23 @@ const git_index_entry_unmerged *git_index_get_unmerged(git_index *index, const c 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); } - -static int read_tree_internal(git_index_tree **out, - const char **buffer_in, const char *buffer_end, git_index_tree *parent) +const git_index_entry_unmerged *git_index_get_unmerged_byindex( + git_index *index, unsigned int n) { - git_index_tree *tree; - const char *name_start, *buffer; - long count; - int error = GIT_SUCCESS; - - if ((tree = git__malloc(sizeof(git_index_tree))) == NULL) - return GIT_ENOMEM; - - memset(tree, 0x0, sizeof(git_index_tree)); - tree->parent = parent; - - buffer = name_start = *buffer_in; - - if ((buffer = memchr(buffer, '\0', buffer_end - buffer)) == NULL) { - error = GIT_EOBJCORRUPTED; - goto cleanup; - } - - /* NUL-terminated tree name */ - tree->name = git__strdup(name_start); - if (tree->name == NULL) { - error = GIT_ENOMEM; - goto cleanup; - } - - if (++buffer >= buffer_end) { - error = GIT_EOBJCORRUPTED; - goto cleanup; - } - - /* Blank-terminated ASCII decimal number of entries in this tree */ - if (git__strtol32(&count, buffer, &buffer, 10) < GIT_SUCCESS || count < -1) { - error = GIT_EOBJCORRUPTED; - goto cleanup; - } - - /* Invalidated TREE. Free the tree but report success */ - if (count == -1) { - /* FIXME: return buffer_end or the end position for - * this single tree entry */ - *buffer_in = buffer_end; - *out = NULL; - free_tree(tree); /* Needs to be done manually */ - return GIT_SUCCESS; - } - - tree->entries = (size_t)count; - - if (*buffer != ' ' || ++buffer >= buffer_end) { - error = GIT_EOBJCORRUPTED; - goto cleanup; - } - - /* Number of children of the tree, newline-terminated */ - if (git__strtol32(&count, buffer, &buffer, 10) < GIT_SUCCESS || - count < 0) { - error = GIT_EOBJCORRUPTED; - goto cleanup; - } - - tree->children_count = (size_t)count; - - if (*buffer != '\n' || ++buffer >= buffer_end) { - error = GIT_EOBJCORRUPTED; - goto cleanup; - } - - /* 160-bit SHA-1 for this tree and it's children */ - if (buffer + GIT_OID_RAWSZ > buffer_end) { - error = GIT_EOBJCORRUPTED; - goto cleanup; - } - - git_oid_mkraw(&tree->oid, (const unsigned char *)buffer); - buffer += GIT_OID_RAWSZ; - - /* Parse children: */ - if (tree->children_count > 0) { - unsigned int i; - int err; - - tree->children = git__malloc(tree->children_count * sizeof(git_index_tree *)); - if (tree->children == NULL) - goto cleanup; - - for (i = 0; i < tree->children_count; ++i) { - err = read_tree_internal(&tree->children[i], &buffer, buffer_end, tree); - - if (err < GIT_SUCCESS) - goto cleanup; - } - } - - *buffer_in = buffer; - *out = tree; - return GIT_SUCCESS; - - cleanup: - free_tree(tree); - return error; + assert(index); + return git_vector_get(&index->unmerged, n); } -static int read_tree(git_index *index, const char *buffer, size_t buffer_size) +static int index_error_invalid(const char *message) { - const char *buffer_end = buffer + buffer_size; - int error; - - error = read_tree_internal(&index->tree, &buffer, buffer_end, NULL); - - if (buffer < buffer_end) - return GIT_EOBJCORRUPTED; - - return error; + 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) @@ -618,53 +550,62 @@ static int read_unmerged(git_index *index, const char *buffer, size_t size) 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++) { - if (git__strtol32((long int *) &lost->mode[i], buffer, &endptr, 8) < GIT_SUCCESS || - !endptr || endptr == buffer || *endptr) - return GIT_ERROR; + int tmp; + + if (git__strtol32(&tmp, buffer, &endptr, 8) < 0 || + !endptr || endptr == buffer || *endptr || + (unsigned)tmp > UINT_MAX) + return index_error_invalid("reading unmerged entry stage"); - len = (endptr + 1) - (char *) buffer; + 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"); - git_oid_mkraw(&lost->oid[i], (unsigned char *) buffer); + 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) @@ -672,15 +613,13 @@ static size_t read_entry(git_index_entry *dest, const void *buffer, size_t buffe size_t path_length, entry_size; uint16_t flags_raw; const char *path_ptr; - const struct entry_short *source; + const struct entry_short *source = buffer; if (INDEX_FOOTER_SIZE + minimal_entry_size > buffer_size) return 0; memset(dest, 0x0, sizeof(git_index_entry)); - source = (const struct entry_short *)(buffer); - dest->ctime.seconds = (git_time_t)ntohl(source->ctime.seconds); dest->ctime.nanoseconds = ntohl(source->ctime.nanoseconds); dest->mtime.seconds = (git_time_t)ntohl(source->mtime.seconds); @@ -695,7 +634,7 @@ static size_t read_entry(git_index_entry *dest, const void *buffer, size_t buffe dest->flags = ntohs(source->flags); if (dest->flags & GIT_IDXENTRY_EXTENDED) { - struct entry_long *source_l = (struct entry_long *)source; + const struct entry_long *source_l = (const struct entry_long *)source; path_ptr = source_l->path; flags_raw = ntohs(source_l->flags_extended); @@ -712,7 +651,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; } @@ -733,20 +672,19 @@ static size_t read_entry(git_index_entry *dest, const void *buffer, size_t buffe static int read_header(struct index_header *dest, const void *buffer) { - const struct index_header *source; - source = (const struct index_header *)(buffer); + const struct index_header *source = 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) @@ -769,10 +707,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 (read_tree(index, 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 @@ -794,21 +732,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, (const void *)buffer, buffer_size - INDEX_FOOTER_SIZE); + 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); @@ -820,23 +758,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) { @@ -846,48 +783,49 @@ 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_mkraw(&checksum_expected, (const unsigned char *)buffer); + 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; } static int write_disk_entry(git_filebuf *file, git_index_entry *entry) { + void *mem = NULL; struct entry_short *ondisk; size_t path_len, disk_size; char *path; @@ -899,21 +837,33 @@ 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, (void **)&ondisk, disk_size) < GIT_SUCCESS) - return GIT_ENOMEM; + if (git_filebuf_reserve(file, &mem, disk_size) < 0) + return -1; + + ondisk = (struct entry_short *)mem; memset(ondisk, 0x0, disk_size); - ondisk->ctime.seconds = htonl((unsigned long)entry->ctime.seconds); - ondisk->mtime.seconds = htonl((unsigned long)entry->mtime.seconds); + /** + * Yes, we have to truncate. + * + * The on-disk format for Index entries clearly defines + * the time and size fields to be 4 bytes each -- so even if + * we store these values with 8 bytes on-memory, they must + * be truncated to 4 bytes before writing to disk. + * + * In 2038 I will be either too dead or too rich to care about this + */ + ondisk->ctime.seconds = htonl((uint32_t)entry->ctime.seconds); + ondisk->mtime.seconds = htonl((uint32_t)entry->mtime.seconds); ondisk->ctime.nanoseconds = htonl(entry->ctime.nanoseconds); ondisk->mtime.nanoseconds = htonl(entry->mtime.nanoseconds); - ondisk->dev = htonl(entry->dev); - ondisk->ino = htonl(entry->ino); + ondisk->dev = htonl(entry->dev); + ondisk->ino = htonl(entry->ino); ondisk->mode = htonl(entry->mode); - ondisk->uid = htonl(entry->uid); - ondisk->gid = htonl(entry->gid); - ondisk->file_size = htonl((unsigned long)entry->file_size); + ondisk->uid = htonl(entry->uid); + ondisk->gid = htonl(entry->gid); + ondisk->file_size = htonl((uint32_t)entry->file_size); git_oid_cpy(&ondisk->oid, &entry->oid); @@ -930,7 +880,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) @@ -940,16 +890,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; @@ -964,11 +913,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) */ @@ -976,12 +925,45 @@ 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) { return (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT; } + +static int read_tree_cb(const char *root, git_tree_entry *tentry, void *data) +{ + git_index *index = data; + git_index_entry *entry = NULL; + git_buf path = GIT_BUF_INIT; + + if (git_tree_entry__is_tree(tentry)) + return 0; + + if (git_buf_joinpath(&path, root, tentry->filename) < 0) + return -1; + + entry = git__calloc(1, sizeof(git_index_entry)); + GITERR_CHECK_ALLOC(entry); + + entry->mode = tentry->attr; + entry->oid = tentry->oid; + entry->path = git_buf_detach(&path); + git_buf_free(&path); + + if (index_insert(index, entry, 0) < 0) { + index_entry_free(entry); + return -1; + } + + return 0; +} + +int git_index_read_tree(git_index *index, git_tree *tree) +{ + git_index_clear(index); + + return git_tree_walk(tree, read_tree_cb, GIT_TREEWALK_POST, index); +} diff --git a/src/index.h b/src/index.h index f2402fd71..8515f4fcb 100644 --- a/src/index.h +++ b/src/index.h @@ -1,36 +1,38 @@ +/* + * 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_index_h__ #define INCLUDE_index_h__ #include "fileops.h" #include "filebuf.h" #include "vector.h" +#include "tree-cache.h" #include "git2/odb.h" #include "git2/index.h" -struct git_index_tree { - char *name; - - struct git_index_tree *parent; - struct git_index_tree **children; - size_t children_count; - - size_t entries; - git_oid oid; -}; - -typedef struct git_index_tree git_index_tree; +#define GIT_INDEX_FILE "index" +#define GIT_INDEX_FILE_MODE 0666 struct git_index { - git_repository *repository; + git_refcount rc; + char *index_file_path; time_t last_modified; git_vector entries; unsigned int on_disk:1; - git_index_tree *tree; + git_tree_cache *tree; git_vector unmerged; }; +extern void git_index__init_entry_from_stat(struct stat *st, git_index_entry *entry); + +extern unsigned int git_index__prefix_position(git_index *index, const char *path); + #endif diff --git a/src/indexer.c b/src/indexer.c new file mode 100644 index 000000000..f0e0a6381 --- /dev/null +++ b/src/indexer.c @@ -0,0 +1,898 @@ +/* + * 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 <zlib.h> + +#include "git2/indexer.h" +#include "git2/object.h" +#include "git2/oid.h" + +#include "common.h" +#include "pack.h" +#include "mwindow.h" +#include "posix.h" +#include "pack.h" +#include "filebuf.h" +#include "sha1.h" + +#define UINT31_MAX (0x7FFFFFFF) + +struct entry { + git_oid oid; + uint32_t crc; + uint32_t offset; + uint64_t offset_long; +}; + +struct git_indexer { + struct git_pack_file *pack; + size_t nr_objects; + git_vector objects; + git_filebuf file; + unsigned int fanout[256]; + 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; +} + +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(pack->mwf.fd, hdr, sizeof(*hdr))) < 0) { + giterr_set(GITERR_OS, "Failed to read in pack header"); + return error; + } + + if (hdr->hdr_signature != ntohl(PACK_SIGNATURE)) { + giterr_set(GITERR_INDEXER, "Wrong pack signature"); + return -1; + } + + if (!pack_version_ok(hdr->hdr_version)) { + giterr_set(GITERR_INDEXER, "Wrong pack version"); + return -1; + } + + return 0; +} + +static int objects_cmp(const void *a, const void *b) +{ + const struct entry *entrya = a; + const struct entry *entryb = b; + + return git_oid_cmp(&entrya->oid, &entryb->oid); +} + +static int cache_cmp(const void *a, const void *b) +{ + const struct git_pack_entry *ea = a; + const struct git_pack_entry *eb = 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; + + 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_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; + + /* + * 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; + + git_mwindow_close(&w); + + /* 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; + + if (type == GIT_OBJ_REF_DELTA) { + idx->off += GIT_OID_RAWSZ; + } else { + git_off_t base_off; + + 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_EBUFS) { + idx->off = entry_start; + return GIT_EBUFS; + } 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_INDEXER, "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_EBUFS) { + idx->off = entry_start; + return 0; + } + + if (error < 0) { + idx->off = entry_start; + error = store_delta(idx); + if (error == GIT_EBUFS) + 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; + } + + return 0; + +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; + + /* Test for this before resolve_deltas(), as it plays with idx->off */ + if (idx->off < idx->pack->mwf.size - GIT_OID_RAWSZ) { + giterr_set(GITERR_INDEXER, "Indexing error: junk at the end of the pack"); + return -1; + } + + if (idx->deltas.length > 0) + if (resolve_deltas(idx, stats) < 0) + return -1; + + if (stats->processed != stats->total) { + giterr_set(GITERR_INDEXER, "Indexing error: early EOF"); + 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 + n = htonl(entry->offset); + + git_filebuf_write(&idx->index_file, &n, sizeof(uint32_t)); + } + + /* 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); + } + + /* 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; + } + + 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); + p_close(idx->pack->mwf.fd); + + 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); + p_close(idx->pack->mwf.fd); + 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; + + 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_INDEXER, "Path is not absolute"); + return -1; + } + + 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); + + /* 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, (unsigned int)idx->nr_objects, objects_cmp); + if (error < 0) + goto cleanup; + + *out = idx; + + return 0; + +cleanup: + git_indexer_free(idx); + + return -1; +} + +static int index_path(git_buf *path, git_indexer *idx) +{ + const char prefix[] = "pack-", suffix[] = ".idx"; + 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; +} + +int git_indexer_write(git_indexer *idx) +{ + git_mwindow *w = NULL; + int error; + 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; + + 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; + + error = git_filebuf_open(&idx->file, filename.ptr, GIT_FILEBUF_HASH_CONTENTS); + 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 < 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 < 0) + goto cleanup; + } + + /* Write out the object names (SHA-1 hashes) */ + SHA1_Init(&ctx); + 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 < 0) + goto cleanup; + } + SHA1_Final(idx->hash.id, &ctx); + + /* 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 < 0) + goto cleanup; + } + + /* 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 + n = htonl(entry->offset); + + error = git_filebuf_write(&idx->file, &n, sizeof(uint32_t)); + if (error < 0) + 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); + + error = git_filebuf_write(&idx->file, &split, sizeof(uint32_t) * 2); + if (error < 0) + goto cleanup; + } + + /* 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); + git_mwindow_close(&w); + if (packfile_hash == NULL) { + error = -1; + goto cleanup; + } + + memcpy(&file_hash, packfile_hash, GIT_OID_RAWSZ); + + 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 < 0) + goto cleanup; + + error = git_filebuf_write(&idx->file, &file_hash, sizeof(git_oid)); + if (error < 0) + goto cleanup; + + /* Figure out what the final name should be */ + error = index_path(&filename, idx); + if (error < 0) + goto cleanup; + + /* Commit file */ + error = git_filebuf_commit_at(&idx->file, filename.ptr, GIT_PACK_FILE_MODE); + +cleanup: + git_mwindow_free_all(&idx->pack->mwf); + if (error < 0) + git_filebuf_cleanup(&idx->file); + git_buf_free(&filename); + + return error; +} + +int git_indexer_run(git_indexer *idx, git_indexer_stats *stats) +{ + git_mwindow_file *mwf; + git_off_t off = sizeof(struct git_pack_header); + int error; + struct entry *entry; + unsigned int left, processed; + + assert(idx && stats); + + mwf = &idx->pack->mwf; + error = git_mwindow_file_register(mwf); + if (error < 0) + return error; + + stats->total = (unsigned int)idx->nr_objects; + stats->processed = processed = 0; + + while (processed < idx->nr_objects) { + git_rawobj obj; + git_oid oid; + struct git_pack_entry *pentry; + git_mwindow *w = NULL; + int i; + git_off_t entry_start = off; + void *packed; + size_t entry_size; + char fmt[GIT_OID_HEXSZ] = {0}; + + 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 = (uint32_t)off; + } + + error = git_packfile_unpack(&obj, idx->pack, &off); + if (error < 0) + goto cleanup; + + /* FIXME: Parse the object instead of hashing it */ + error = git_odb__hashobj(&oid, &obj); + if (error < 0) { + giterr_set(GITERR_INDEXER, "Failed to hash object"); + goto cleanup; + } + + pentry = git__malloc(sizeof(struct git_pack_entry)); + if (pentry == NULL) { + error = -1; + goto cleanup; + } + + git_oid_cpy(&pentry->sha1, &oid); + pentry->offset = entry_start; + git_oid_fmt(fmt, &oid); + error = git_vector_insert(&idx->pack->cache, pentry); + if (error < 0) + goto cleanup; + + git_oid_cpy(&entry->oid, &oid); + entry->crc = crc32(0L, Z_NULL, 0); + + entry_size = (size_t)(off - entry_start); + packed = git_mwindow_open(mwf, &w, entry_start, entry_size, &left); + if (packed == NULL) { + error = -1; + goto cleanup; + } + 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 < 0) + goto cleanup; + + for (i = oid.id[0]; i < 256; ++i) { + idx->fanout[i]++; + } + + git__free(obj.data); + + stats->processed = ++processed; + } + +cleanup: + git_mwindow_free_all(mwf); + + return error; + +} + +void git_indexer_free(git_indexer *idx) +{ + unsigned int i; + struct entry *e; + struct git_pack_entry *pe; + + 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__free(idx->pack); + git__free(idx); +} + diff --git a/src/iterator.c b/src/iterator.c new file mode 100644 index 000000000..819b0e22a --- /dev/null +++ b/src/iterator.c @@ -0,0 +1,748 @@ +/* + * 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 "iterator.h" +#include "tree.h" +#include "ignore.h" +#include "buffer.h" +#include "git2/submodule.h" + +#define ITERATOR_BASE_INIT(P,NAME_LC,NAME_UC) do { \ + (P) = git__calloc(1, sizeof(NAME_LC ## _iterator)); \ + GITERR_CHECK_ALLOC(P); \ + (P)->base.type = GIT_ITERATOR_ ## NAME_UC; \ + (P)->base.start = start ? git__strdup(start) : NULL; \ + (P)->base.end = end ? git__strdup(end) : NULL; \ + (P)->base.current = NAME_LC ## _iterator__current; \ + (P)->base.at_end = NAME_LC ## _iterator__at_end; \ + (P)->base.advance = NAME_LC ## _iterator__advance; \ + (P)->base.seek = NAME_LC ## _iterator__seek; \ + (P)->base.reset = NAME_LC ## _iterator__reset; \ + (P)->base.free = NAME_LC ## _iterator__free; \ + if ((start && !(P)->base.start) || (end && !(P)->base.end)) \ + return -1; \ + } while (0) + + +static int empty_iterator__no_item( + git_iterator *iter, const git_index_entry **entry) +{ + GIT_UNUSED(iter); + *entry = NULL; + return 0; +} + +static int empty_iterator__at_end(git_iterator *iter) +{ + GIT_UNUSED(iter); + return 1; +} + +static int empty_iterator__noop(git_iterator *iter) +{ + GIT_UNUSED(iter); + return 0; +} + +static int empty_iterator__seek(git_iterator *iter, const char *prefix) +{ + GIT_UNUSED(iter); + GIT_UNUSED(prefix); + return -1; +} + +static void empty_iterator__free(git_iterator *iter) +{ + GIT_UNUSED(iter); +} + +int git_iterator_for_nothing(git_iterator **iter) +{ + git_iterator *i = git__calloc(1, sizeof(git_iterator)); + GITERR_CHECK_ALLOC(i); + + i->type = GIT_ITERATOR_EMPTY; + i->current = empty_iterator__no_item; + i->at_end = empty_iterator__at_end; + i->advance = empty_iterator__no_item; + i->seek = empty_iterator__seek; + i->reset = empty_iterator__noop; + i->free = empty_iterator__free; + + *iter = i; + + return 0; +} + + +typedef struct tree_iterator_frame tree_iterator_frame; +struct tree_iterator_frame { + tree_iterator_frame *next; + git_tree *tree; + char *start; + unsigned int index; +}; + +typedef struct { + git_iterator base; + git_repository *repo; + tree_iterator_frame *stack; + git_index_entry entry; + git_buf path; + bool path_has_filename; +} tree_iterator; + +static const git_tree_entry *tree_iterator__tree_entry(tree_iterator *ti) +{ + return (ti->stack == NULL) ? NULL : + git_tree_entry_byindex(ti->stack->tree, ti->stack->index); +} + +static char *tree_iterator__current_filename( + tree_iterator *ti, const git_tree_entry *te) +{ + if (!ti->path_has_filename) { + if (git_buf_joinpath(&ti->path, ti->path.ptr, te->filename) < 0) + return NULL; + ti->path_has_filename = true; + } + + return ti->path.ptr; +} + +static void tree_iterator__pop_frame(tree_iterator *ti) +{ + tree_iterator_frame *tf = ti->stack; + ti->stack = tf->next; + if (ti->stack != NULL) /* don't free the initial tree */ + git_tree_free(tf->tree); + git__free(tf); +} + +static int tree_iterator__to_end(tree_iterator *ti) +{ + while (ti->stack && ti->stack->next) + tree_iterator__pop_frame(ti); + + if (ti->stack) + ti->stack->index = git_tree_entrycount(ti->stack->tree); + + return 0; +} + +static int tree_iterator__current( + git_iterator *self, const git_index_entry **entry) +{ + tree_iterator *ti = (tree_iterator *)self; + const git_tree_entry *te = tree_iterator__tree_entry(ti); + + if (entry) + *entry = NULL; + + if (te == NULL) + return 0; + + ti->entry.mode = te->attr; + git_oid_cpy(&ti->entry.oid, &te->oid); + + ti->entry.path = tree_iterator__current_filename(ti, te); + if (ti->entry.path == NULL) + return -1; + + if (ti->base.end && git__prefixcmp(ti->entry.path, ti->base.end) > 0) + return tree_iterator__to_end(ti); + + if (entry) + *entry = &ti->entry; + + return 0; +} + +static int tree_iterator__at_end(git_iterator *self) +{ + return (tree_iterator__tree_entry((tree_iterator *)self) == NULL); +} + +static tree_iterator_frame *tree_iterator__alloc_frame( + git_tree *tree, char *start) +{ + tree_iterator_frame *tf = git__calloc(1, sizeof(tree_iterator_frame)); + if (!tf) + return NULL; + + tf->tree = tree; + + if (start && *start) { + tf->start = start; + tf->index = git_tree__prefix_position(tree, start); + } + + return tf; +} + +static int tree_iterator__expand_tree(tree_iterator *ti) +{ + int error; + git_tree *subtree; + const git_tree_entry *te = tree_iterator__tree_entry(ti); + tree_iterator_frame *tf; + char *relpath; + + while (te != NULL && git_tree_entry__is_tree(te)) { + if (git_buf_joinpath(&ti->path, ti->path.ptr, te->filename) < 0) + return -1; + + /* check that we have not passed the range end */ + if (ti->base.end != NULL && + git__prefixcmp(ti->path.ptr, ti->base.end) > 0) + return tree_iterator__to_end(ti); + + if ((error = git_tree_lookup(&subtree, ti->repo, &te->oid)) < 0) + return error; + + relpath = NULL; + + /* apply range start to new frame if relevant */ + if (ti->stack->start && + git__prefixcmp(ti->stack->start, te->filename) == 0) + { + size_t namelen = strlen(te->filename); + if (ti->stack->start[namelen] == '/') + relpath = ti->stack->start + namelen + 1; + } + + if ((tf = tree_iterator__alloc_frame(subtree, relpath)) == NULL) + return -1; + + tf->next = ti->stack; + ti->stack = tf; + + te = tree_iterator__tree_entry(ti); + } + + return 0; +} + +static int tree_iterator__advance( + git_iterator *self, const git_index_entry **entry) +{ + int error = 0; + tree_iterator *ti = (tree_iterator *)self; + const git_tree_entry *te = NULL; + + if (entry != NULL) + *entry = NULL; + + if (ti->path_has_filename) { + git_buf_rtruncate_at_char(&ti->path, '/'); + ti->path_has_filename = false; + } + + while (ti->stack != NULL) { + te = git_tree_entry_byindex(ti->stack->tree, ++ti->stack->index); + if (te != NULL) + break; + + tree_iterator__pop_frame(ti); + + git_buf_rtruncate_at_char(&ti->path, '/'); + } + + if (te && git_tree_entry__is_tree(te)) + error = tree_iterator__expand_tree(ti); + + if (!error) + error = tree_iterator__current(self, entry); + + return error; +} + +static int tree_iterator__seek(git_iterator *self, const char *prefix) +{ + GIT_UNUSED(self); + GIT_UNUSED(prefix); + /* pop stack until matches prefix */ + /* seek item in current frame matching prefix */ + /* push stack which matches prefix */ + return -1; +} + +static void tree_iterator__free(git_iterator *self) +{ + tree_iterator *ti = (tree_iterator *)self; + while (ti->stack != NULL) + tree_iterator__pop_frame(ti); + git_buf_free(&ti->path); +} + +static int tree_iterator__reset(git_iterator *self) +{ + tree_iterator *ti = (tree_iterator *)self; + + while (ti->stack && ti->stack->next) + tree_iterator__pop_frame(ti); + + if (ti->stack) + ti->stack->index = + git_tree__prefix_position(ti->stack->tree, ti->base.start); + + git_buf_clear(&ti->path); + + return tree_iterator__expand_tree(ti); +} + +int git_iterator_for_tree_range( + git_iterator **iter, + git_repository *repo, + git_tree *tree, + const char *start, + const char *end) +{ + int error; + tree_iterator *ti; + + if (tree == NULL) + return git_iterator_for_nothing(iter); + + ITERATOR_BASE_INIT(ti, tree, TREE); + + ti->repo = repo; + ti->stack = tree_iterator__alloc_frame(tree, ti->base.start); + + if ((error = tree_iterator__expand_tree(ti)) < 0) + git_iterator_free((git_iterator *)ti); + else + *iter = (git_iterator *)ti; + + return error; +} + + +typedef struct { + git_iterator base; + git_index *index; + unsigned int current; +} index_iterator; + +static int index_iterator__current( + git_iterator *self, const git_index_entry **entry) +{ + index_iterator *ii = (index_iterator *)self; + git_index_entry *ie = git_index_get(ii->index, ii->current); + + if (ie != NULL && + ii->base.end != NULL && + git__prefixcmp(ie->path, ii->base.end) > 0) + { + ii->current = git_index_entrycount(ii->index); + ie = NULL; + } + + if (entry) + *entry = ie; + + return 0; +} + +static int index_iterator__at_end(git_iterator *self) +{ + index_iterator *ii = (index_iterator *)self; + return (ii->current >= git_index_entrycount(ii->index)); +} + +static int index_iterator__advance( + git_iterator *self, const git_index_entry **entry) +{ + index_iterator *ii = (index_iterator *)self; + + if (ii->current < git_index_entrycount(ii->index)) + ii->current++; + + return index_iterator__current(self, entry); +} + +static int index_iterator__seek(git_iterator *self, const char *prefix) +{ + GIT_UNUSED(self); + GIT_UNUSED(prefix); + /* find last item before prefix */ + return -1; +} + +static int index_iterator__reset(git_iterator *self) +{ + index_iterator *ii = (index_iterator *)self; + ii->current = 0; + return 0; +} + +static void index_iterator__free(git_iterator *self) +{ + index_iterator *ii = (index_iterator *)self; + git_index_free(ii->index); + ii->index = NULL; +} + +int git_iterator_for_index_range( + git_iterator **iter, + git_repository *repo, + const char *start, + const char *end) +{ + int error; + index_iterator *ii; + + ITERATOR_BASE_INIT(ii, index, INDEX); + + if ((error = git_repository_index(&ii->index, repo)) < 0) + git__free(ii); + else { + ii->current = start ? git_index__prefix_position(ii->index, start) : 0; + *iter = (git_iterator *)ii; + } + + return error; +} + + +typedef struct workdir_iterator_frame workdir_iterator_frame; +struct workdir_iterator_frame { + workdir_iterator_frame *next; + git_vector entries; + unsigned int index; + char *start; +}; + +typedef struct { + git_iterator base; + git_repository *repo; + size_t root_len; + workdir_iterator_frame *stack; + git_ignores ignores; + git_index_entry entry; + git_buf path; + int is_ignored; +} workdir_iterator; + +static workdir_iterator_frame *workdir_iterator__alloc_frame(void) +{ + workdir_iterator_frame *wf = git__calloc(1, sizeof(workdir_iterator_frame)); + if (wf == NULL) + return NULL; + if (git_vector_init(&wf->entries, 0, git_path_with_stat_cmp) != 0) { + git__free(wf); + return NULL; + } + return wf; +} + +static void workdir_iterator__free_frame(workdir_iterator_frame *wf) +{ + unsigned int i; + git_path_with_stat *path; + + git_vector_foreach(&wf->entries, i, path) + git__free(path); + git_vector_free(&wf->entries); + git__free(wf); +} + +static int workdir_iterator__update_entry(workdir_iterator *wi); + +static int workdir_iterator__entry_cmp(const void *prefix, const void *item) +{ + const git_path_with_stat *ps = item; + return git__prefixcmp((const char *)prefix, ps->path); +} + +static int workdir_iterator__expand_dir(workdir_iterator *wi) +{ + int error; + workdir_iterator_frame *wf = workdir_iterator__alloc_frame(); + GITERR_CHECK_ALLOC(wf); + + error = git_path_dirload_with_stat(wi->path.ptr, wi->root_len, &wf->entries); + if (error < 0 || wf->entries.length == 0) { + workdir_iterator__free_frame(wf); + return GIT_ENOTFOUND; + } + + git_vector_sort(&wf->entries); + + if (!wi->stack) + wf->start = wi->base.start; + else if (wi->stack->start && + git__prefixcmp(wi->stack->start, wi->path.ptr + wi->root_len) == 0) + wf->start = wi->stack->start; + + if (wf->start) + git_vector_bsearch3( + &wf->index, &wf->entries, workdir_iterator__entry_cmp, wf->start); + + wf->next = wi->stack; + wi->stack = wf; + + /* only push new ignores if this is not top level directory */ + if (wi->stack->next != NULL) { + ssize_t slash_pos = git_buf_rfind_next(&wi->path, '/'); + (void)git_ignore__push_dir(&wi->ignores, &wi->path.ptr[slash_pos + 1]); + } + + return workdir_iterator__update_entry(wi); +} + +static int workdir_iterator__current( + git_iterator *self, const git_index_entry **entry) +{ + workdir_iterator *wi = (workdir_iterator *)self; + *entry = (wi->entry.path == NULL) ? NULL : &wi->entry; + return 0; +} + +static int workdir_iterator__at_end(git_iterator *self) +{ + return (((workdir_iterator *)self)->entry.path == NULL); +} + +static int workdir_iterator__advance( + git_iterator *self, const git_index_entry **entry) +{ + int error; + workdir_iterator *wi = (workdir_iterator *)self; + workdir_iterator_frame *wf; + git_path_with_stat *next; + + if (entry != NULL) + *entry = NULL; + + if (wi->entry.path == NULL) + return 0; + + while ((wf = wi->stack) != NULL) { + next = git_vector_get(&wf->entries, ++wf->index); + if (next != NULL) { + if (strcmp(next->path, DOT_GIT "/") == 0) + continue; + /* else found a good entry */ + break; + } + + /* pop workdir directory stack */ + wi->stack = wf->next; + workdir_iterator__free_frame(wf); + git_ignore__pop_dir(&wi->ignores); + + if (wi->stack == NULL) { + memset(&wi->entry, 0, sizeof(wi->entry)); + return 0; + } + } + + error = workdir_iterator__update_entry(wi); + + if (!error && entry != NULL) + error = workdir_iterator__current(self, entry); + + return error; +} + +static int workdir_iterator__seek(git_iterator *self, const char *prefix) +{ + GIT_UNUSED(self); + GIT_UNUSED(prefix); + /* pop stack until matching prefix */ + /* find prefix item in current frame */ + /* push subdirectories as deep as possible while matching */ + return 0; +} + +static int workdir_iterator__reset(git_iterator *self) +{ + workdir_iterator *wi = (workdir_iterator *)self; + while (wi->stack != NULL && wi->stack->next != NULL) { + workdir_iterator_frame *wf = wi->stack; + wi->stack = wf->next; + workdir_iterator__free_frame(wf); + git_ignore__pop_dir(&wi->ignores); + } + if (wi->stack) + wi->stack->index = 0; + return 0; +} + +static void workdir_iterator__free(git_iterator *self) +{ + workdir_iterator *wi = (workdir_iterator *)self; + + while (wi->stack != NULL) { + workdir_iterator_frame *wf = wi->stack; + wi->stack = wf->next; + workdir_iterator__free_frame(wf); + } + + git_ignore__free(&wi->ignores); + git_buf_free(&wi->path); +} + +static int workdir_iterator__update_entry(workdir_iterator *wi) +{ + git_path_with_stat *ps = git_vector_get(&wi->stack->entries, wi->stack->index); + + git_buf_truncate(&wi->path, wi->root_len); + memset(&wi->entry, 0, sizeof(wi->entry)); + + if (!ps) + return 0; + + if (git_buf_put(&wi->path, ps->path, ps->path_len) < 0) + return -1; + + if (wi->base.end && + git__prefixcmp(wi->path.ptr + wi->root_len, wi->base.end) > 0) + return 0; + + wi->entry.path = ps->path; + + /* skip over .git directory */ + if (strcmp(ps->path, DOT_GIT "/") == 0) + return workdir_iterator__advance((git_iterator *)wi, NULL); + + /* if there is an error processing the entry, treat as ignored */ + wi->is_ignored = 1; + + 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); + + /* if this is a file type we don't handle, treat as ignored */ + if (wi->entry.mode == 0) + return 0; + + /* okay, we are far enough along to look up real ignore rule */ + 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)) { + 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(); + } + + /* 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_range( + git_iterator **iter, + git_repository *repo, + const char *start, + const char *end) +{ + int error; + workdir_iterator *wi; + + assert(iter && repo); + + if (git_repository_is_bare(repo)) { + giterr_set(GITERR_INVALID, + "Cannot scan working directory for bare repo"); + return -1; + } + + ITERATOR_BASE_INIT(wi, workdir, WORKDIR); + + wi->repo = repo; + + 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 -1; + } + + wi->root_len = wi->path.size; + + if ((error = workdir_iterator__expand_dir(wi)) < 0) { + if (error == GIT_ENOTFOUND) + error = 0; + else { + git_iterator_free((git_iterator *)wi); + wi = NULL; + } + } + + *iter = (git_iterator *)wi; + + return error; +} + + +int git_iterator_current_tree_entry( + git_iterator *iter, const git_tree_entry **tree_entry) +{ + *tree_entry = (iter->type != GIT_ITERATOR_TREE) ? NULL : + tree_iterator__tree_entry((tree_iterator *)iter); + return 0; +} + +int git_iterator_current_is_ignored(git_iterator *iter) +{ + return (iter->type != GIT_ITERATOR_WORKDIR) ? 0 : + ((workdir_iterator *)iter)->is_ignored; +} + +int git_iterator_advance_into_directory( + git_iterator *iter, const git_index_entry **entry) +{ + workdir_iterator *wi = (workdir_iterator *)iter; + + if (iter->type == GIT_ITERATOR_WORKDIR && + wi->entry.path && + S_ISDIR(wi->entry.mode) && + !S_ISGITLINK(wi->entry.mode)) + { + 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) : 0; +} + +int git_iterator_cmp( + git_iterator *iter, const char *path_prefix) +{ + const git_index_entry *entry; + + /* a "done" iterator is after every prefix */ + if (git_iterator_current(iter, &entry) < 0 || + entry == NULL) + return 1; + + /* a NULL prefix is after any valid iterator */ + if (!path_prefix) + return -1; + + return git__prefixcmp(entry->path, path_prefix); +} + diff --git a/src/iterator.h b/src/iterator.h new file mode 100644 index 000000000..b916a9080 --- /dev/null +++ b/src/iterator.h @@ -0,0 +1,151 @@ +/* + * 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_iterator_h__ +#define INCLUDE_iterator_h__ + +#include "common.h" +#include "git2/index.h" + +typedef struct git_iterator git_iterator; + +typedef enum { + GIT_ITERATOR_EMPTY = 0, + GIT_ITERATOR_TREE = 1, + GIT_ITERATOR_INDEX = 2, + GIT_ITERATOR_WORKDIR = 3 +} git_iterator_type_t; + +struct git_iterator { + git_iterator_type_t type; + char *start; + char *end; + int (*current)(git_iterator *, const git_index_entry **); + int (*at_end)(git_iterator *); + int (*advance)(git_iterator *, const git_index_entry **); + int (*seek)(git_iterator *, const char *prefix); + int (*reset)(git_iterator *); + void (*free)(git_iterator *); +}; + +extern int git_iterator_for_nothing(git_iterator **iter); + +extern int git_iterator_for_tree_range( + git_iterator **iter, git_repository *repo, git_tree *tree, + const char *start, const char *end); + +GIT_INLINE(int) git_iterator_for_tree( + git_iterator **iter, git_repository *repo, git_tree *tree) +{ + return git_iterator_for_tree_range(iter, repo, tree, NULL, NULL); +} + +extern int git_iterator_for_index_range( + git_iterator **iter, git_repository *repo, + const char *start, const char *end); + +GIT_INLINE(int) git_iterator_for_index( + git_iterator **iter, git_repository *repo) +{ + return git_iterator_for_index_range(iter, repo, NULL, NULL); +} + +extern int git_iterator_for_workdir_range( + git_iterator **iter, git_repository *repo, + const char *start, const char *end); + +GIT_INLINE(int) git_iterator_for_workdir( + git_iterator **iter, git_repository *repo) +{ + return git_iterator_for_workdir_range(iter, repo, NULL, NULL); +} + + +/* Entry is not guaranteed to be fully populated. For a tree iterator, + * we will only populate the mode, oid and path, for example. For a workdir + * iterator, we will not populate the oid. + * + * You do not need to free the entry. It is still "owned" by the iterator. + * Once you call `git_iterator_advance`, then content of the old entry is + * no longer guaranteed to be valid. + */ +GIT_INLINE(int) git_iterator_current( + git_iterator *iter, const git_index_entry **entry) +{ + return iter->current(iter, entry); +} + +GIT_INLINE(int) git_iterator_at_end(git_iterator *iter) +{ + return iter->at_end(iter); +} + +GIT_INLINE(int) git_iterator_advance( + git_iterator *iter, const git_index_entry **entry) +{ + return iter->advance(iter, entry); +} + +GIT_INLINE(int) git_iterator_seek( + git_iterator *iter, const char *prefix) +{ + return iter->seek(iter, prefix); +} + +GIT_INLINE(int) git_iterator_reset(git_iterator *iter) +{ + return iter->reset(iter); +} + +GIT_INLINE(void) git_iterator_free(git_iterator *iter) +{ + if (iter == NULL) + return; + + iter->free(iter); + + git__free(iter->start); + git__free(iter->end); + + memset(iter, 0, sizeof(*iter)); + + git__free(iter); +} + +GIT_INLINE(git_iterator_type_t) git_iterator_type(git_iterator *iter) +{ + return iter->type; +} + +extern int git_iterator_current_tree_entry( + git_iterator *iter, const git_tree_entry **tree_entry); + +extern int git_iterator_current_is_ignored(git_iterator *iter); + +/** + * Iterate into a workdir directory. + * + * Workdir iterators do not automatically descend into directories (so that + * when comparing two iterator entries you can detect a newly created + * directory in the workdir). As a result, you may get S_ISDIR items from + * a workdir iterator. If you wish to iterate over the contents of the + * directories you encounter, then call this function when you encounter + * a directory. + * + * If there are no files in the directory, this will end up acting like a + * regular advance and will skip past the directory, so you should be + * prepared for that case. + * + * On non-workdir iterators or if not pointing at a directory, this is a + * no-op and will not advance the iterator. + */ +extern int git_iterator_advance_into_directory( + git_iterator *iter, const git_index_entry **entry); + +extern int git_iterator_cmp( + git_iterator *iter, const char *path_prefix); + +#endif 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 */ @@ -1,31 +1,42 @@ +/* + * 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_map_h__ #define INCLUDE_map_h__ #include "common.h" -/* git__mmap() prot values */ -#define GIT_PROT_NONE 0x0 -#define GIT_PROT_READ 0x1 +/* p_mmap() prot values */ +#define GIT_PROT_NONE 0x0 +#define GIT_PROT_READ 0x1 #define GIT_PROT_WRITE 0x2 -#define GIT_PROT_EXEC 0x4 +#define GIT_PROT_EXEC 0x4 /* git__mmmap() flags values */ -#define GIT_MAP_FILE 0 -#define GIT_MAP_SHARED 1 +#define GIT_MAP_FILE 0 +#define GIT_MAP_SHARED 1 #define GIT_MAP_PRIVATE 2 -#define GIT_MAP_TYPE 0xf -#define GIT_MAP_FIXED 0x10 +#define GIT_MAP_TYPE 0xf +#define GIT_MAP_FIXED 0x10 -typedef struct { /* memory mapped buffer */ - void *data; /* data bytes */ - size_t len; /* data length */ +typedef struct { /* memory mapped buffer */ + void *data; /* data bytes */ + size_t len; /* data length */ #ifdef GIT_WIN32 - HANDLE fmh; /* file mapping handle */ + HANDLE fmh; /* file mapping handle */ #endif } git_map; -extern int git__mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset); -extern int git__munmap(git_map *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); #endif /* INCLUDE_map_h__ */ diff --git a/src/message.c b/src/message.c new file mode 100644 index 000000000..aa0220fd0 --- /dev/null +++ b/src/message.c @@ -0,0 +1,61 @@ +/* + * 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 "message.h" +#include <ctype.h> + +static size_t line_length_without_trailing_spaces(const char *line, size_t len) +{ + while (len) { + unsigned char c = line[len - 1]; + if (!git__isspace(c)) + break; + len--; + } + + return len; +} + +/* Greatly inspired from git.git "stripspace" */ +/* see https://github.com/git/git/blob/497215d8811ac7b8955693ceaad0899ecd894ed2/builtin/stripspace.c#L4-67 */ +int git_message_prettify(git_buf *message_out, const char *message, int strip_comments) +{ + const size_t message_len = strlen(message); + + int consecutive_empty_lines = 0; + size_t i, line_length, rtrimmed_line_length; + char *next_newline; + + for (i = 0; i < strlen(message); i += line_length) { + next_newline = memchr(message + i, '\n', message_len - i); + + if (next_newline != NULL) { + line_length = next_newline - (message + i) + 1; + } else { + line_length = message_len - i; + } + + if (strip_comments && line_length && message[i] == '#') + continue; + + rtrimmed_line_length = line_length_without_trailing_spaces(message + i, line_length); + + if (!rtrimmed_line_length) { + consecutive_empty_lines++; + continue; + } + + if (consecutive_empty_lines > 0 && message_out->size > 0) + git_buf_putc(message_out, '\n'); + + consecutive_empty_lines = 0; + git_buf_put(message_out, message + i, rtrimmed_line_length); + git_buf_putc(message_out, '\n'); + } + + return git_buf_oom(message_out) ? -1 : 0; +} diff --git a/src/message.h b/src/message.h new file mode 100644 index 000000000..ddfa13e18 --- /dev/null +++ b/src/message.h @@ -0,0 +1,14 @@ +/* + * 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_message_h__ +#define INCLUDE_message_h__ + +#include "buffer.h" + +int git_message_prettify(git_buf *message_out, const char *message, int strip_comments); + +#endif /* INCLUDE_message_h__ */ diff --git a/src/mingw-compat.h b/src/mingw-compat.h deleted file mode 100644 index b7919c2e8..000000000 --- a/src/mingw-compat.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef INCLUDE_mingw_compat__ -#define INCLUDE_mingw_compat__ - -#if defined(__MINGW32__) - -/* use a 64-bit file offset type */ -# define lseek _lseeki64 -# define stat _stati64 -# define fstat _fstati64 - -#endif - -#endif /* INCLUDE_mingw_compat__ */ diff --git a/src/msvc-compat.h b/src/msvc-compat.h deleted file mode 100644 index 1ec85f91b..000000000 --- a/src/msvc-compat.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef INCLUDE_msvc_compat__ -#define INCLUDE_msvc_compat__ - -#if defined(_MSC_VER) - -/* access() mode parameter #defines */ -# define F_OK 0 /* existence check */ -# define W_OK 2 /* write mode check */ -# define R_OK 4 /* read mode check */ - -# define lseek _lseeki64 -# define stat _stat64 -# define fstat _fstat64 - -/* stat: file mode type testing macros */ -# define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR) -# define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG) -# define S_ISFIFO(m) (((m) & _S_IFMT) == _S_IFIFO) - -/* case-insensitive string comparison */ -# define strcasecmp _stricmp -# define strncasecmp _strnicmp - -#if (_MSC_VER >= 1600) -# include <stdint.h> -#else -/* add some missing <stdint.h> typedef's */ -typedef signed char int8_t; -typedef unsigned char uint8_t; - -typedef short int16_t; -typedef unsigned short uint16_t; - -typedef long int32_t; -typedef unsigned long uint32_t; - -typedef long long int64_t; -typedef unsigned long long uint64_t; - -typedef long long intmax_t; -typedef unsigned long long uintmax_t; -#endif - -#endif - -#endif /* INCLUDE_msvc_compat__ */ diff --git a/src/mwindow.c b/src/mwindow.c new file mode 100644 index 000000000..57adabd48 --- /dev/null +++ b/src/mwindow.c @@ -0,0 +1,270 @@ +/* + * 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 "mwindow.h" +#include "vector.h" +#include "fileops.h" +#include "map.h" +#include "global.h" + +#define DEFAULT_WINDOW_SIZE \ + (sizeof(void*) >= 8 \ + ? 1 * 1024 * 1024 * 1024 \ + : 32 * 1024 * 1024) + +#define DEFAULT_MAPPED_LIMIT \ + ((1024 * 1024) * (sizeof(void*) >= 8 ? 8192ULL : 256UL)) + +/* + * These are the global options for mmmap limits. + * TODO: allow the user to change these + */ +static struct { + size_t window_size; + size_t mapped_limit; +} _mw_options = { + DEFAULT_WINDOW_SIZE, + DEFAULT_MAPPED_LIMIT, +}; + +/* + * Free all the windows in a sequence, typically because we're done + * with the file + */ +void git_mwindow_free_all(git_mwindow_file *mwf) +{ + git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl; + unsigned int i; + /* + * Remove these windows from the global list + */ + for (i = 0; i < ctl->windowfiles.length; ++i){ + if (git_vector_get(&ctl->windowfiles, i) == mwf) { + git_vector_remove(&ctl->windowfiles, i); + break; + } + } + + if (ctl->windowfiles.length == 0) { + git_vector_free(&ctl->windowfiles); + ctl->windowfiles.contents = NULL; + } + + while (mwf->windows) { + git_mwindow *w = mwf->windows; + assert(w->inuse_cnt == 0); + + ctl->mapped -= w->window_map.len; + ctl->open_windows--; + + git_futils_mmap_free(&w->window_map); + + mwf->windows = w->next; + git__free(w); + } +} + +/* + * Check if a window 'win' contains the address 'offset' + */ +int git_mwindow_contains(git_mwindow *win, git_off_t offset) +{ + git_off_t win_off = win->offset; + return win_off <= offset + && offset <= (git_off_t)(win_off + win->window_map.len); +} + +/* + * Find the least-recently-used window in a file + */ +void git_mwindow_scan_lru( + git_mwindow_file *mwf, + git_mwindow **lru_w, + git_mwindow **lru_l) +{ + git_mwindow *w, *w_l; + + for (w_l = NULL, w = mwf->windows; w; w = w->next) { + if (!w->inuse_cnt) { + /* + * If the current one is more recent than the last one, + * store it in the output parameter. If lru_w is NULL, + * it's the first loop, so store it as well. + */ + if (!*lru_w || w->last_used < (*lru_w)->last_used) { + *lru_w = w; + *lru_l = w_l; + } + } + w_l = w; + } +} + +/* + * Close the least recently used window. You should check to see if + * the file descriptors need closing from time to time. + */ +static int git_mwindow_close_lru(git_mwindow_file *mwf) +{ + git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl; + unsigned int i; + git_mwindow *lru_w = NULL, *lru_l = NULL, **list = &mwf->windows; + + /* FIXME: Does this give us any advantage? */ + if(mwf->windows) + git_mwindow_scan_lru(mwf, &lru_w, &lru_l); + + for (i = 0; i < ctl->windowfiles.length; ++i) { + git_mwindow *last = lru_w; + git_mwindow_file *cur = git_vector_get(&ctl->windowfiles, i); + git_mwindow_scan_lru(cur, &lru_w, &lru_l); + if (lru_w != last) + list = &cur->windows; + } + + if (!lru_w) { + giterr_set(GITERR_OS, "Failed to close memory window. Couldn't find LRU"); + return -1; + } + + ctl->mapped -= lru_w->window_map.len; + git_futils_mmap_free(&lru_w->window_map); + + if (lru_l) + lru_l->next = lru_w->next; + else + *list = lru_w->next; + + git__free(lru_w); + ctl->open_windows--; + + return 0; +} + +static git_mwindow *new_window( + git_mwindow_file *mwf, + git_file fd, + git_off_t size, + git_off_t offset) +{ + git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl; + size_t walign = _mw_options.window_size / 2; + git_off_t len; + git_mwindow *w; + + w = git__malloc(sizeof(*w)); + if (w == NULL) + return NULL; + + memset(w, 0x0, sizeof(*w)); + w->offset = (offset / walign) * walign; + + len = size - w->offset; + if (len > (git_off_t)_mw_options.window_size) + len = (git_off_t)_mw_options.window_size; + + ctl->mapped += (size_t)len; + + while (_mw_options.mapped_limit < ctl->mapped && + git_mwindow_close_lru(mwf) == 0) /* nop */; + + /* + * We treat _mw_options.mapped_limit as a soft limit. If we can't find a + * window to close and are above the limit, we still mmap the new + * window. + */ + + 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++; + + if (ctl->mapped > ctl->peak_mapped) + ctl->peak_mapped = ctl->mapped; + + if (ctl->open_windows > ctl->peak_open_windows) + ctl->peak_open_windows = ctl->open_windows; + + return w; +} + +/* + * Open a new window, closing the least recenty used until we have + * enough space. Don't forget to add it to your list + */ +unsigned char *git_mwindow_open( + git_mwindow_file *mwf, + git_mwindow **cursor, + git_off_t offset, + 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) { + w->inuse_cnt--; + } + + for (w = mwf->windows; w; w = w->next) { + if (git_mwindow_contains(w, offset) && + git_mwindow_contains(w, offset + extra)) + break; + } + + /* + * If there isn't a suitable window, we need to create a new + * one. + */ + if (!w) { + w = new_window(mwf, mwf->fd, mwf->size, offset); + if (w == NULL) + return NULL; + w->next = mwf->windows; + mwf->windows = w; + } + } + + /* If we changed w, store it in the cursor */ + if (w != *cursor) { + w->last_used = ctl->used_ctr++; + w->inuse_cnt++; + *cursor = w; + } + + offset -= w->offset; + + if (left) + *left = (unsigned int)(w->window_map.len - offset); + + 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; + + if (ctl->windowfiles.length == 0 && + git_vector_init(&ctl->windowfiles, 8, NULL) < 0) + return -1; + + return git_vector_insert(&ctl->windowfiles, mwf); +} + +void git_mwindow_close(git_mwindow **window) +{ + git_mwindow *w = *window; + if (w) { + w->inuse_cnt--; + *window = NULL; + } +} diff --git a/src/mwindow.h b/src/mwindow.h new file mode 100644 index 000000000..058027251 --- /dev/null +++ b/src/mwindow.h @@ -0,0 +1,45 @@ +/* + * 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_mwindow__ +#define INCLUDE_mwindow__ + +#include "map.h" +#include "vector.h" + +typedef struct git_mwindow { + struct git_mwindow *next; + git_map window_map; + git_off_t offset; + size_t last_used; + size_t inuse_cnt; +} git_mwindow; + +typedef struct git_mwindow_file { + git_mwindow *windows; + int fd; + git_off_t size; +} git_mwindow_file; + +typedef struct git_mwindow_ctl { + size_t mapped; + unsigned int open_windows; + unsigned int mmap_calls; + unsigned int peak_open_windows; + size_t peak_mapped; + size_t used_ctr; + git_vector windowfiles; +} 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, 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); + +#endif diff --git a/src/netops.c b/src/netops.c new file mode 100644 index 000000000..e6f3e5627 --- /dev/null +++ b/src/netops.c @@ -0,0 +1,517 @@ +/* + * 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 _WIN32 +# include <sys/types.h> +# include <sys/socket.h> +# include <sys/select.h> +# include <sys/time.h> +# include <netdb.h> +# include <arpa/inet.h> +#else +# include <winsock2.h> +# include <ws2tcpip.h> +# ifdef _MSC_VER +# pragma comment(lib, "ws2_32.lib") +# endif +#endif + +#ifdef GIT_SSL +# include <openssl/ssl.h> +# include <openssl/x509v3.h> +#endif + +#include <ctype.h> +#include "git2/errors.h" + +#include "common.h" +#include "netops.h" +#include "posix.h" +#include "buffer.h" +#include "transport.h" + +#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 + +#ifdef GIT_SSL +static int ssl_set_error(gitno_ssl *ssl, int error) +{ + int err; + err = SSL_get_error(ssl->ssl, error); + giterr_set(GITERR_NET, "SSL error: %s", ERR_error_string(err, NULL)); + return -1; +} +#endif + +void gitno_buffer_setup(git_transport *t, gitno_buffer *buf, char *data, unsigned int len) +{ + memset(buf, 0x0, sizeof(gitno_buffer)); + memset(data, 0x0, len); + buf->data = data; + buf->len = len; + buf->offset = 0; + buf->fd = t->socket; +#ifdef GIT_SSL + if (t->encrypt) + buf->ssl = &t->ssl; +#endif +} + +#ifdef GIT_SSL +static int ssl_recv(gitno_ssl *ssl, void *data, size_t len) +{ + int ret; + + do { + ret = SSL_read(ssl->ssl, data, len); + } while (SSL_get_error(ssl->ssl, ret) == SSL_ERROR_WANT_READ); + + if (ret < 0) + return ssl_set_error(ssl, ret); + + return ret; +} +#endif + +int gitno_recv(gitno_buffer *buf) +{ + int ret; + +#ifdef GIT_SSL + if (buf->ssl != NULL) { + if ((ret = ssl_recv(buf->ssl, buf->data + buf->offset, buf->len - buf->offset)) < 0) + return -1; + } else { + 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; + } + } +#else + 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; + } +#endif + + buf->offset += ret; + return ret; +} + +/* Consume up to ptr and move the rest of the buffer to the beginning */ +void gitno_consume(gitno_buffer *buf, const char *ptr) +{ + size_t consumed; + + assert(ptr - buf->data >= 0); + assert(ptr - buf->data <= (int) buf->len); + + consumed = ptr - buf->data; + + memmove(buf->data, ptr, buf->offset - consumed); + memset(buf->data + buf->offset, 0x0, buf->len - buf->offset); + buf->offset -= consumed; +} + +/* Consume const bytes and move the rest of the buffer to the beginning */ +void gitno_consume_n(gitno_buffer *buf, size_t cons) +{ + memmove(buf->data, buf->data + cons, buf->len - buf->offset); + memset(buf->data + cons, 0x0, buf->len - buf->offset); + buf->offset -= cons; +} + +int gitno_ssl_teardown(git_transport *t) +{ +#ifdef GIT_SSL + int ret; +#endif + + if (!t->encrypt) + return 0; + +#ifdef GIT_SSL + + do { + ret = SSL_shutdown(t->ssl.ssl); + } while (ret == 0); + if (ret < 0) + return ssl_set_error(&t->ssl, ret); + + SSL_free(t->ssl.ssl); + SSL_CTX_free(t->ssl.ctx); +#endif + return 0; +} + + +#ifdef GIT_SSL +/* Match host names according to RFC 2818 rules */ +static int match_host(const char *pattern, const char *host) +{ + for (;;) { + char c = tolower(*pattern++); + + if (c == '\0') + return *host ? -1 : 0; + + if (c == '*') { + c = *pattern; + /* '*' at the end matches everything left */ + if (c == '\0') + return 0; + + /* + * We've found a pattern, so move towards the next matching + * char. The '.' is handled specially because wildcards aren't + * allowed to cross subdomains. + */ + + while(*host) { + char h = tolower(*host); + if (c == h) + return match_host(pattern, host++); + if (h == '.') + return match_host(pattern, host); + host++; + } + return -1; + } + + if (c != tolower(*host++)) + return -1; + } + + return -1; +} + +static int check_host_name(const char *name, const char *host) +{ + if (!strcasecmp(name, host)) + return 0; + + if (match_host(name, host) < 0) + return -1; + + return 0; +} + +static int verify_server_cert(git_transport *t, const char *host) +{ + X509 *cert; + X509_NAME *peer_name; + ASN1_STRING *str; + unsigned char *peer_cn = NULL; + int matched = -1, type = GEN_DNS; + GENERAL_NAMES *alts; + struct in6_addr addr6; + struct in_addr addr4; + void *addr; + int i = -1,j; + + + /* Try to parse the host as an IP address to see if it is */ + if (inet_pton(AF_INET, host, &addr4)) { + type = GEN_IPADD; + addr = &addr4; + } else { + if(inet_pton(AF_INET6, host, &addr6)) { + type = GEN_IPADD; + addr = &addr6; + } + } + + + cert = SSL_get_peer_certificate(t->ssl.ssl); + + /* Check the alternative names */ + alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); + if (alts) { + int num; + + num = sk_GENERAL_NAME_num(alts); + for (i = 0; i < num && matched != 1; i++) { + const GENERAL_NAME *gn = sk_GENERAL_NAME_value(alts, i); + const char *name = (char *) ASN1_STRING_data(gn->d.ia5); + size_t namelen = (size_t) ASN1_STRING_length(gn->d.ia5); + + /* Skip any names of a type we're not looking for */ + if (gn->type != type) + continue; + + if (type == GEN_DNS) { + /* If it contains embedded NULs, don't even try */ + if (memchr(name, '\0', namelen)) + continue; + + if (check_host_name(name, host) < 0) + matched = 0; + else + matched = 1; + } else if (type == GEN_IPADD) { + /* Here name isn't so much a name but a binary representation of the IP */ + matched = !!memcmp(name, addr, namelen); + } + } + } + GENERAL_NAMES_free(alts); + + if (matched == 0) + goto on_error; + + if (matched == 1) + return 0; + + /* If no alternative names are available, check the common name */ + peer_name = X509_get_subject_name(cert); + if (peer_name == NULL) + goto on_error; + + if (peer_name) { + /* Get the index of the last CN entry */ + while ((j = X509_NAME_get_index_by_NID(peer_name, NID_commonName, i)) >= 0) + i = j; + } + + if (i < 0) + goto on_error; + + str = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(peer_name, i)); + if (str == NULL) + goto on_error; + + /* Work around a bug in OpenSSL whereby ASN1_STRING_to_UTF8 fails if it's already in utf-8 */ + if (ASN1_STRING_type(str) == V_ASN1_UTF8STRING) { + int size = ASN1_STRING_length(str); + + if (size > 0) { + peer_cn = OPENSSL_malloc(size + 1); + GITERR_CHECK_ALLOC(peer_cn); + memcpy(peer_cn, ASN1_STRING_data(str), size); + peer_cn[size] = '\0'; + } + } else { + int size = ASN1_STRING_to_UTF8(&peer_cn, str); + GITERR_CHECK_ALLOC(peer_cn); + if (memchr(peer_cn, '\0', size)) + goto cert_fail; + } + + if (check_host_name((char *)peer_cn, host) < 0) + goto cert_fail; + + OPENSSL_free(peer_cn); + + return 0; + +on_error: + OPENSSL_free(peer_cn); + return ssl_set_error(&t->ssl, 0); + +cert_fail: + OPENSSL_free(peer_cn); + giterr_set(GITERR_SSL, "Certificate host name check failed"); + return -1; +} + +static int ssl_setup(git_transport *t, const char *host) +{ + int ret; + + SSL_library_init(); + SSL_load_error_strings(); + t->ssl.ctx = SSL_CTX_new(SSLv23_method()); + if (t->ssl.ctx == NULL) + return ssl_set_error(&t->ssl, 0); + + SSL_CTX_set_mode(t->ssl.ctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_verify(t->ssl.ctx, SSL_VERIFY_PEER, NULL); + if (!SSL_CTX_set_default_verify_paths(t->ssl.ctx)) + return ssl_set_error(&t->ssl, 0); + + t->ssl.ssl = SSL_new(t->ssl.ctx); + if (t->ssl.ssl == NULL) + return ssl_set_error(&t->ssl, 0); + + if((ret = SSL_set_fd(t->ssl.ssl, t->socket)) == 0) + return ssl_set_error(&t->ssl, ret); + + if ((ret = SSL_connect(t->ssl.ssl)) <= 0) + return ssl_set_error(&t->ssl, ret); + + if (t->check_cert && verify_server_cert(t, host) < 0) + return -1; + + return 0; +} +#else +static int ssl_setup(git_transport *t, const char *host) +{ + GIT_UNUSED(t); + GIT_UNUSED(host); + return 0; +} +#endif + +int gitno_connect(git_transport *t, const char *host, const char *port) +{ + struct addrinfo *info = NULL, *p; + struct addrinfo hints; + int ret; + GIT_SOCKET s = INVALID_SOCKET; + + memset(&hints, 0x0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + if ((ret = getaddrinfo(host, port, &hints, &info)) < 0) { + giterr_set(GITERR_NET, "Failed to resolve address for %s: %s", host, gai_strerror(ret)); + return -1; + } + + for (p = info; p != NULL; p = p->ai_next) { + s = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if (s == INVALID_SOCKET) { + net_set_error("error creating socket"); + break; + } + + if (connect(s, p->ai_addr, (socklen_t)p->ai_addrlen) == 0) + break; + + /* If we can't connect, try the next one */ + gitno_close(s); + s = INVALID_SOCKET; + } + + /* Oops, we couldn't connect to any address */ + if (s == INVALID_SOCKET && p == NULL) { + giterr_set(GITERR_OS, "Failed to connect to %s", host); + return -1; + } + + t->socket = s; + freeaddrinfo(info); + + if (t->encrypt && ssl_setup(t, host) < 0) + return -1; + + return 0; +} + +#ifdef GIT_SSL +static int send_ssl(gitno_ssl *ssl, const char *msg, size_t len) +{ + int ret; + size_t off = 0; + + while (off < len) { + ret = SSL_write(ssl->ssl, msg + off, len - off); + if (ret <= 0) + return ssl_set_error(ssl, ret); + + off += ret; + } + + return off; +} +#endif + +int gitno_send(git_transport *t, const char *msg, size_t len, int flags) +{ + int ret; + size_t off = 0; + +#ifdef GIT_SSL + if (t->encrypt) + return send_ssl(&t->ssl, msg, len); +#endif + + while (off < len) { + errno = 0; + ret = p_send(t->socket, msg + off, len - off, flags); + if (ret < 0) { + net_set_error("Error sending data"); + return -1; + } + + off += ret; + } + + return (int)off; +} + + +#ifdef GIT_WIN32 +int gitno_close(GIT_SOCKET s) +{ + return closesocket(s) == SOCKET_ERROR ? -1 : 0; +} +#else +int gitno_close(GIT_SOCKET s) +{ + return close(s); +} +#endif + +int gitno_select_in(gitno_buffer *buf, long int sec, long int usec) +{ + fd_set fds; + struct timeval tv; + + tv.tv_sec = sec; + tv.tv_usec = usec; + + FD_ZERO(&fds); + FD_SET(buf->fd, &fds); + + /* The select(2) interface is silly */ + 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; + + colon = strchr(url, ':'); + slash = strchr(url, '/'); + + 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); + } + GITERR_CHECK_ALLOC(*port); + + delim = colon == NULL ? slash : colon; + *host = git__strndup(url, delim - url); + GITERR_CHECK_ALLOC(*host); + + return 0; +} diff --git a/src/netops.h b/src/netops.h new file mode 100644 index 000000000..4976f87f8 --- /dev/null +++ b/src/netops.h @@ -0,0 +1,39 @@ +/* + * 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_netops_h__ +#define INCLUDE_netops_h__ + +#include "posix.h" +#include "transport.h" +#include "common.h" + +typedef struct gitno_buffer { + char *data; + size_t len; + size_t offset; + GIT_SOCKET fd; +#ifdef GIT_SSL + struct gitno_ssl *ssl; +#endif +} gitno_buffer; + +void gitno_buffer_setup(git_transport *t, gitno_buffer *buf, char *data, unsigned int len); +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(git_transport *t, const char *host, const char *port); +int gitno_send(git_transport *t, const char *msg, size_t len, int flags); +int gitno_close(GIT_SOCKET s); +int gitno_ssl_teardown(git_transport *t); +int gitno_send_chunk_size(int s, size_t len); +int gitno_select_in(gitno_buffer *buf, long int sec, long int usec); + +int gitno_extract_host_and_port(char **host, char **port, const char *url, const char *default_port); + +#endif diff --git a/src/notes.c b/src/notes.c new file mode 100644 index 000000000..84ad94087 --- /dev/null +++ b/src/notes.c @@ -0,0 +1,548 @@ +/* + * 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 "notes.h" + +#include "git2.h" +#include "refs.h" +#include "config.h" +#include "iterator.h" + +static int find_subtree(git_tree **subtree, const git_oid *root, + git_repository *repo, const char *target, int *fanout) +{ + int error; + unsigned int i; + git_tree *tree; + const git_tree_entry *entry; + + *subtree = NULL; + + error = git_tree_lookup(&tree, repo, root); + if (error < 0) + return error; + + for (i=0; i<git_tree_entrycount(tree); i++) { + entry = git_tree_entry_byindex(tree, i); + + if (!git__ishex(git_tree_entry_name(entry))) + continue; + + /* + * A notes tree follows a strict byte-based progressive fanout + * (i.e. using 2/38, 2/2/36, etc. fanouts, not e.g. 4/36 fanout) + */ + + if (S_ISDIR(git_tree_entry_attributes(entry)) + && strlen(git_tree_entry_name(entry)) == 2 + && !strncmp(git_tree_entry_name(entry), target + *fanout, 2)) { + + /* found matching subtree - unpack and resume lookup */ + + git_oid subtree_sha; + git_oid_cpy(&subtree_sha, git_tree_entry_id(entry)); + git_tree_free(tree); + + *fanout += 2; + + return find_subtree(subtree, &subtree_sha, repo, + target, fanout); + } + } + + *subtree = tree; + return 0; +} + +static int find_blob(git_oid *blob, git_tree *tree, const char *target) +{ + unsigned int i; + const git_tree_entry *entry; + + for (i=0; i<git_tree_entrycount(tree); i++) { + entry = git_tree_entry_byindex(tree, i); + + if (!strcmp(git_tree_entry_name(entry), target)) { + /* found matching note object - return */ + + git_oid_cpy(blob, git_tree_entry_id(entry)); + return 0; + } + } + return GIT_ENOTFOUND; +} + +static int note_write(git_oid *out, git_repository *repo, + git_signature *author, git_signature *committer, + const char *notes_ref, const char *note, + const git_oid *tree_sha, const char *target, + int nparents, git_commit **parents) +{ + int error, fanout = 0; + git_oid oid; + git_tree *tree = NULL; + git_tree_entry *entry; + git_treebuilder *tb; + + /* check for existing notes tree */ + + if (tree_sha) { + error = find_subtree(&tree, tree_sha, repo, target, &fanout); + if (error < 0) + return error; + + error = find_blob(&oid, tree, target + fanout); + if (error != GIT_ENOTFOUND) { + git_tree_free(tree); + if (!error) { + giterr_set(GITERR_REPOSITORY, "Note for '%s' exists already", target); + error = GIT_EEXISTS; + } + return error; + } + } + + /* no matching tree entry - add note object to target tree */ + + error = git_treebuilder_create(&tb, tree); + git_tree_free(tree); + + if (error < 0) + return error; + + if (!tree_sha) + /* no notes tree yet - create fanout */ + fanout += 2; + + /* create note object */ + error = git_blob_create_frombuffer(&oid, repo, note, strlen(note)); + if (error < 0) { + git_treebuilder_free(tb); + return error; + } + + error = git_treebuilder_insert(&entry, tb, target + fanout, &oid, 0100644); + if (error < 0) { + /* libgit2 doesn't support object removal (gc) yet */ + /* we leave an orphaned blob object behind - TODO */ + + git_treebuilder_free(tb); + return error; + } + + if (out) + git_oid_cpy(out, git_tree_entry_id(entry)); + + error = git_treebuilder_write(&oid, repo, tb); + git_treebuilder_free(tb); + + if (error < 0) + return 0; + + if (!tree_sha) { + /* create fanout subtree */ + + char subtree[3]; + strncpy(subtree, target, 2); + subtree[2] = '\0'; + + error = git_treebuilder_create(&tb, NULL); + if (error < 0) + return error; + + error = git_treebuilder_insert(NULL, tb, subtree, &oid, 0040000); + if (error < 0) { + git_treebuilder_free(tb); + return error; + } + + error = git_treebuilder_write(&oid, repo, tb); + + git_treebuilder_free(tb); + + if (error < 0) + return error; + } + + /* create new notes commit */ + + error = git_tree_lookup(&tree, repo, &oid); + if (error < 0) + return error; + + error = git_commit_create(&oid, repo, notes_ref, author, committer, + NULL, GIT_NOTES_DEFAULT_MSG_ADD, + tree, nparents, (const git_commit **) parents); + + git_tree_free(tree); + + return error; +} + +static int note_lookup(git_note **out, git_repository *repo, + const git_oid *tree_sha, const char *target) +{ + int error, fanout = 0; + git_oid oid; + git_blob *blob; + git_tree *tree; + git_note *note; + + error = find_subtree(&tree, tree_sha, repo, target, &fanout); + if (error < 0) + return error; + + error = find_blob(&oid, tree, target + fanout); + + git_tree_free(tree); + if (error < 0) + return error; + + error = git_blob_lookup(&blob, repo, &oid); + if (error < 0) + return error; + + note = git__malloc(sizeof(git_note)); + GITERR_CHECK_ALLOC(note); + + git_oid_cpy(¬e->oid, &oid); + note->message = git__strdup(git_blob_rawcontent(blob)); + GITERR_CHECK_ALLOC(note->message); + + *out = note; + + git_blob_free(blob); + return error; +} + +static int note_remove(git_repository *repo, + git_signature *author, git_signature *committer, + const char *notes_ref, const git_oid *tree_sha, + const char *target, int nparents, git_commit **parents) +{ + int error, fanout = 0; + git_oid oid; + git_tree *tree; + git_treebuilder *tb; + + error = find_subtree(&tree, tree_sha, repo, target, &fanout); + if (error < 0) + return error; + + error = find_blob(&oid, tree, target + fanout); + if (!error) + error = git_treebuilder_create(&tb, tree); + + git_tree_free(tree); + if (error < 0) + return error; + + error = git_treebuilder_remove(tb, target + fanout); + if (!error) + error = git_treebuilder_write(&oid, repo, tb); + + git_treebuilder_free(tb); + if (error < 0) + return error; + + /* create new notes commit */ + + error = git_tree_lookup(&tree, repo, &oid); + if (error < 0) + return error; + + error = git_commit_create(&oid, repo, notes_ref, author, committer, + NULL, GIT_NOTES_DEFAULT_MSG_RM, + tree, nparents, (const git_commit **) parents); + + git_tree_free(tree); + + return error; +} + +static int note_get_default_ref(const char **out, git_repository *repo) +{ + int ret; + git_config *cfg; + + *out = NULL; + + if (git_repository_config__weakptr(&cfg, repo) < 0) + return -1; + + ret = git_config_get_string(out, cfg, "core.notesRef"); + if (ret == GIT_ENOTFOUND) { + *out = GIT_NOTES_DEFAULT_REF; + return 0; + } + + return ret; +} + +static int normalize_namespace(const char **notes_ref, git_repository *repo) +{ + if (*notes_ref) + return 0; + + return note_get_default_ref(notes_ref, repo); +} + +static int retrieve_note_tree_oid(git_oid *tree_oid_out, git_repository *repo, const char *notes_ref) +{ + int error = -1; + git_commit *commit = NULL; + git_oid oid; + + if ((error = git_reference_name_to_oid(&oid, repo, notes_ref)) < 0) + goto cleanup; + + if (git_commit_lookup(&commit, repo, &oid) < 0) + goto cleanup; + + git_oid_cpy(tree_oid_out, git_commit_tree_oid(commit)); + + error = 0; + +cleanup: + git_commit_free(commit); + return error; +} + +int git_note_read(git_note **out, git_repository *repo, + const char *notes_ref, const git_oid *oid) +{ + int error; + char *target; + git_oid sha; + + *out = NULL; + + if (normalize_namespace(¬es_ref, repo) < 0) + return -1; + + if ((error = retrieve_note_tree_oid(&sha, repo, notes_ref)) < 0) + return error; + + target = git_oid_allocfmt(oid); + GITERR_CHECK_ALLOC(target); + + error = note_lookup(out, repo, &sha, target); + + git__free(target); + 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 error, nparents = 0; + char *target; + git_oid sha; + git_commit *commit = NULL; + git_reference *ref; + + if (normalize_namespace(¬es_ref, repo) < 0) + return -1; + + error = git_reference_lookup(&ref, repo, notes_ref); + if (error < 0 && error != GIT_ENOTFOUND) + return error; + + if (!error) { + assert(git_reference_type(ref) == GIT_REF_OID); + + /* lookup existing notes tree oid */ + + git_oid_cpy(&sha, git_reference_oid(ref)); + git_reference_free(ref); + + error = git_commit_lookup(&commit, repo, &sha); + if (error < 0) + return error; + + git_oid_cpy(&sha, git_commit_tree_oid(commit)); + nparents++; + } + + target = git_oid_allocfmt(oid); + GITERR_CHECK_ALLOC(target); + + error = note_write(out, repo, author, committer, notes_ref, + note, nparents ? &sha : NULL, target, + nparents, &commit); + + git__free(target); + git_commit_free(commit); + return error; +} + +int git_note_remove(git_repository *repo, const char *notes_ref, + git_signature *author, git_signature *committer, + const git_oid *oid) +{ + int error; + char *target; + git_oid sha; + git_commit *commit; + git_reference *ref; + + if (normalize_namespace(¬es_ref, repo) < 0) + return -1; + + error = git_reference_lookup(&ref, repo, notes_ref); + if (error < 0) + return error; + + assert(git_reference_type(ref) == GIT_REF_OID); + + git_oid_cpy(&sha, git_reference_oid(ref)); + git_reference_free(ref); + + error = git_commit_lookup(&commit, repo, &sha); + if (error < 0) + return error; + + git_oid_cpy(&sha, git_commit_tree_oid(commit)); + + target = git_oid_allocfmt(oid); + 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; +} + +int git_note_default_ref(const char **out, git_repository *repo) +{ + assert(repo); + return note_get_default_ref(out, repo); +} + +const char * git_note_message(git_note *note) +{ + assert(note); + return note->message; +} + +const git_oid * git_note_oid(git_note *note) +{ + assert(note); + return ¬e->oid; +} + +void git_note_free(git_note *note) +{ + if (note == NULL) + return; + + git__free(note->message); + git__free(note); +} + +static int process_entry_path( + const char* entry_path, + const git_oid *note_oid, + int (*note_cb)(git_note_data *note_data, void *payload), + void *payload) +{ + int i = 0, j = 0, error = -1, len; + git_buf buf = GIT_BUF_INIT; + git_note_data note_data; + + if (git_buf_puts(&buf, entry_path) < 0) + goto cleanup; + + len = git_buf_len(&buf); + + while (i < len) { + if (buf.ptr[i] == '/') { + i++; + continue; + } + + if (git__fromhex(buf.ptr[i]) < 0) { + /* This is not a note entry */ + error = 0; + goto cleanup; + } + + if (i != j) + buf.ptr[j] = buf.ptr[i]; + + i++; + j++; + } + + buf.ptr[j] = '\0'; + buf.size = j; + + if (j != GIT_OID_HEXSZ) { + /* This is not a note entry */ + error = 0; + goto cleanup; + } + + if (git_oid_fromstr(¬e_data.annotated_object_oid, buf.ptr) < 0) + return -1; + + git_oid_cpy(¬e_data.blob_oid, note_oid); + + error = note_cb(¬e_data, payload); + +cleanup: + git_buf_free(&buf); + return error; +} + +int git_note_foreach( + git_repository *repo, + const char *notes_ref, + int (*note_cb)(git_note_data *note_data, void *payload), + void *payload) +{ + int error = -1; + git_oid tree_oid; + git_iterator *iter = NULL; + git_tree *tree = NULL; + const git_index_entry *item; + + if (normalize_namespace(¬es_ref, repo) < 0) + return -1; + + if ((error = retrieve_note_tree_oid(&tree_oid, repo, notes_ref)) < 0) + goto cleanup; + + if (git_tree_lookup(&tree, repo, &tree_oid) < 0) + goto cleanup; + + if (git_iterator_for_tree(&iter, repo, tree) < 0) + goto cleanup; + + if (git_iterator_current(iter, &item) < 0) + goto cleanup; + + while (item) { + if (process_entry_path(item->path, &item->oid, note_cb, payload) < 0) + goto cleanup; + + if (git_iterator_advance(iter, &item) < 0) + goto cleanup; + } + + error = 0; + +cleanup: + git_iterator_free(iter); + git_tree_free(tree); + return error; +} diff --git a/src/notes.h b/src/notes.h new file mode 100644 index 000000000..219db1ab0 --- /dev/null +++ b/src/notes.h @@ -0,0 +1,28 @@ +/* + * 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_note_h__ +#define INCLUDE_note_h__ + +#include "common.h" + +#include "git2/oid.h" + +#define GIT_NOTES_DEFAULT_REF "refs/notes/commits" + +#define GIT_NOTES_DEFAULT_MSG_ADD \ + "Notes added by 'git_note_create' from libgit2" + +#define GIT_NOTES_DEFAULT_MSG_RM \ + "Notes removed by 'git_note_remove' from libgit2" + +struct git_note { + git_oid oid; + + char *message; +}; + +#endif /* INCLUDE_notes_h__ */ diff --git a/src/object.c b/src/object.c index d14ca8566..d3673eda0 100644 --- a/src/object.c +++ b/src/object.c @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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 <stdarg.h> @@ -37,8 +19,8 @@ static const int OBJECT_BASE_SIZE = 4096; static struct { - const char *str; /* type name string */ - int loose; /* valid loose object type flag */ + const char *str; /* type name string */ + int loose; /* valid loose object type flag */ size_t size; /* size in bytes of the object structure */ } git_objects_table[] = { /* 0 = GIT_OBJ__EXT1 */ @@ -80,47 +62,59 @@ static int create_object(git_object **object_out, git_otype type) case GIT_OBJ_BLOB: case GIT_OBJ_TREE: object = git__malloc(git_object__size(type)); - if (object == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(object); memset(object, 0x0, git_object__size(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; *object_out = object; - return GIT_SUCCESS; + return 0; } -int git_object_lookup_prefix(git_object **object_out, git_repository *repo, const git_oid *id, unsigned int len, git_otype type) +int git_object_lookup_prefix( + git_object **object_out, + git_repository *repo, + const git_oid *id, + unsigned int len, + git_otype type) { git_object *object = NULL; + git_odb *odb = NULL; git_odb_object *odb_obj; - int error = GIT_SUCCESS; + int error = 0; 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 < 0) + return error; if (len > GIT_OID_HEXSZ) len = GIT_OID_HEXSZ; - if (len == GIT_OID_HEXSZ) { + if (len == GIT_OID_HEXSZ) { /* We want to match the full id : we can first look up in the cache, * since there is no need to check for non ambiguousity */ object = git_cache_get(&repo->objects, id); if (object != NULL) { - if (type != GIT_OBJ_ANY && type != object->type) - return git__throw(GIT_EINVALIDTYPE, "Failed to lookup object. The given type does not match the type on the ODB"); + if (type != GIT_OBJ_ANY && type != object->type) { + git_object_free(object); + giterr_set(GITERR_ODB, "The given type does not match the type in ODB"); + return GIT_ENOTFOUND; + } *object_out = object; - return GIT_SUCCESS; + return 0; } /* Object was not found in the cache, let's explore the backends. @@ -128,7 +122,7 @@ int git_object_lookup_prefix(git_object **object_out, git_repository *repo, cons * it is the same cost for packed and loose object backends, * but it may be much more costly for sqlite and hiredis. */ - error = git_odb_read(&odb_obj, repo->db, id); + error = git_odb_read(&odb_obj, odb, id); } else { git_oid short_oid; @@ -141,28 +135,29 @@ int git_object_lookup_prefix(git_object **object_out, git_repository *repo, cons /* If len < GIT_OID_HEXSZ (a strict short oid was given), we have * 2 options : * - We always search in the cache first. If we find that short oid is - * ambiguous, we can stop. But in all the other cases, we must then - * explore all the backends (to find an object if there was match, - * or to check that oid is not ambiguous if we have found 1 match in - * the cache) + * ambiguous, we can stop. But in all the other cases, we must then + * explore all the backends (to find an object if there was match, + * or to check that oid is not ambiguous if we have found 1 match in + * the cache) * - We never explore the cache, go right to exploring the backends * We chose the latter : we explore directly the backends. */ - error = git_odb_read_prefix(&odb_obj, repo->db, &short_oid, len); + 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 error; if (type != GIT_OBJ_ANY && type != odb_obj->raw.type) { - git_odb_object_close(odb_obj); - return git__throw(GIT_EINVALIDTYPE, "Failed to lookup object. The given type does not match the type on the ODB"); + git_odb_object_free(odb_obj); + giterr_set(GITERR_ODB, "The given type does not match the type on the ODB"); + return GIT_ENOTFOUND; } 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); @@ -189,15 +184,15 @@ int git_object_lookup_prefix(git_object **object_out, git_repository *repo, cons break; } - git_odb_object_close(odb_obj); + 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) { @@ -228,12 +223,12 @@ void git_object__free(void *_obj) break; default: - free(object); + git__free(object); break; } } -void git_object_close(git_object *object) +void git_object_free(git_object *object) { if (object == NULL) return; @@ -297,3 +292,42 @@ size_t git_object__size(git_otype type) return git_objects_table[type].size; } +int git_object__resolve_to_type(git_object **obj, git_otype type) +{ + int error = 0; + git_object *scan, *next; + + if (type == GIT_OBJ_ANY) + return 0; + + scan = *obj; + + while (!error && scan && git_object_type(scan) != type) { + + switch (git_object_type(scan)) { + case GIT_OBJ_COMMIT: + { + git_tree *tree = NULL; + error = git_commit_tree(&tree, (git_commit *)scan); + next = (git_object *)tree; + break; + } + + case GIT_OBJ_TAG: + error = git_tag_target(&next, (git_tag *)scan); + break; + + default: + giterr_set(GITERR_REFERENCE, "Object does not resolve to type"); + error = -1; + next = NULL; + break; + } + + git_object_free(scan); + scan = next; + } + + *obj = scan; + return error; +} @@ -1,30 +1,12 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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/zlib.h" +#include <zlib.h> #include "git2/object.h" #include "fileops.h" #include "hash.h" @@ -46,45 +28,37 @@ typedef struct int is_alternate; } backend_internal; -static int format_object_header(char *hdr, size_t n, git_rawobj *obj) +static int format_object_header(char *hdr, size_t n, size_t obj_len, git_otype obj_type) { - const char *type_str = git_object_type2string(obj->type); - 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! */ - - if (len < 0 || ((size_t) len) >= n) - return git__throw(GIT_ERROR, "Cannot format object header. Length is out of bounds"); + const char *type_str = git_object_type2string(obj_type); + int len = p_snprintf(hdr, n, "%s %"PRIuZ, type_str, obj_len); + assert(len > 0 && len <= (int)n); return len+1; } -int git_odb__hash_obj(git_oid *id, char *hdr, size_t n, int *len, git_rawobj *obj) +int git_odb__hashobj(git_oid *id, git_rawobj *obj) { git_buf_vec vec[2]; - int hdrlen; + char header[64]; + int hdrlen; - assert(id && hdr && len && 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"); - - if ((hdrlen = format_object_header(hdr, n, obj)) < 0) - return git__rethrow(hdrlen, "Failed to hash object"); + return -1; - *len = hdrlen; + hdrlen = format_object_header(header, sizeof(header), obj->len, obj->type); - vec[0].data = hdr; - vec[0].len = hdrlen; + vec[0].data = header; + vec[0].len = hdrlen; vec[1].data = obj->data; - vec[1].len = obj->len; + vec[1].len = obj->len; git_hash_vec(id, vec, 2); - return GIT_SUCCESS; + return 0; } @@ -104,8 +78,8 @@ static void free_odb_object(void *o) git_odb_object *object = (git_odb_object *)o; if (object != NULL) { - free(object->raw.data); - free(object); + git__free(object->raw.data); + git__free(object); } } @@ -129,15 +103,104 @@ git_otype git_odb_object_type(git_odb_object *object) return object->raw.type; } -void git_odb_object_close(git_odb_object *object) +void git_odb_object_free(git_odb_object *object) { git_cached_obj_decref((git_cached_obj *)object, &free_odb_object); } +int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type) +{ + int hdr_len; + char hdr[64], buffer[2048]; + git_hash_ctx *ctx; + + hdr_len = format_object_header(hdr, sizeof(hdr), size, type); + + ctx = git_hash_new_ctx(); + + git_hash_update(ctx, hdr, hdr_len); + + while (size > 0) { + ssize_t read_len = read(fd, buffer, sizeof(buffer)); + + if (read_len < 0) { + git_hash_free_ctx(ctx); + giterr_set(GITERR_OS, "Error reading file"); + return -1; + } + + git_hash_update(ctx, buffer, read_len); + size -= read_len; + } + + git_hash_final(out, ctx); + git_hash_free_ctx(ctx); + + return 0; +} + +int git_odb__hashlink(git_oid *out, const char *path) +{ + struct stat st; + git_off_t size; + int result; + + if (git_path_lstat(path, &st) < 0) + return -1; + + size = st.st_size; + + 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); + GITERR_CHECK_ALLOC(link_data); + + read_len = p_readlink(path, link_data, (size_t)(size + 1)); + if (read_len != (ssize_t)size) { + giterr_set(GITERR_OS, "Failed to read symlink data for '%s'", path); + return -1; + } + + result = git_odb_hash(out, link_data, (size_t)size, GIT_OBJ_BLOB); + git__free(link_data); + } else { + 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 result; +} + +int git_odb_hashfile(git_oid *out, const char *path, git_otype type) +{ + git_off_t size; + 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 -1; + } + + result = git_odb__hashfd(out, fd, (size_t)size, type); + p_close(fd); + return result; +} + int git_odb_hash(git_oid *id, const void *data, size_t len, git_otype type) { - char hdr[64]; - int hdrlen; git_rawobj raw; assert(id); @@ -146,7 +209,7 @@ int git_odb_hash(git_oid *id, const void *data, size_t len, git_otype type) raw.len = len; raw.type = type; - return git_odb__hash_obj(id, hdr, sizeof(hdr), &hdrlen, &raw); + return git_odb__hashobj(id, &raw); } /** @@ -171,19 +234,19 @@ 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) { fake_wstream *stream = (fake_wstream *)_stream; - free(stream->buffer); - free(stream); + git__free(stream->buffer); + git__free(stream); } static int init_fake_wstream(git_odb_stream **stream_p, git_odb_backend *backend, size_t size, git_otype type) @@ -191,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) { - free(stream); - return GIT_ENOMEM; + git__free(stream); + return -1; } stream->stream.backend = backend; @@ -210,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; } /*********************************************************** @@ -223,8 +285,8 @@ static int init_fake_wstream(git_odb_stream **stream_p, git_odb_backend *backend static int backend_sort_cmp(const void *a, const void *b) { - const backend_internal *backend_a = *(const backend_internal **)(a); - const backend_internal *backend_b = *(const backend_internal **)(b); + const backend_internal *backend_a = (const backend_internal *)(a); + const backend_internal *backend_b = (const backend_internal *)(b); if (backend_a->is_alternate == backend_b->is_alternate) return (backend_b->priority - backend_a->priority); @@ -234,25 +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) { - 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) { - free(db); - return git__rethrow(error, "Failed to create object database"); + 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 -1; } *out = db; - return GIT_SUCCESS; + GIT_REFCOUNT_INC(db); + return 0; } static int add_backend_internal(git_odb *odb, git_odb_backend *backend, int priority, int is_alternate) @@ -261,25 +317,24 @@ 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; internal->is_alternate = is_alternate; if (git_vector_insert(&odb->backends, internal) < 0) { - free(internal); - return GIT_ENOMEM; + git__free(internal); + 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) @@ -295,130 +350,125 @@ 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); - 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) { - char alternates_path[GIT_PATH_MAX]; - char *buffer, *alternate; - - gitfo_buf alternates_buf = GITFO_BUF_INIT; - int error; + git_buf alternates_path = GIT_BUF_INIT; + git_buf alternates_buf = GIT_BUF_INIT; + char *buffer; + const char *alternate; + int result = 0; - git__joinpath(alternates_path, objects_dir, GIT_ALTERNATES_FILE); + if (git_buf_joinpath(&alternates_path, objects_dir, GIT_ALTERNATES_FILE) < 0) + return -1; - if (gitfo_exists(alternates_path) < GIT_SUCCESS) - return GIT_SUCCESS; + if (git_path_exists(alternates_path.ptr) == false) { + git_buf_free(&alternates_path); + return 0; + } - if (gitfo_read_file(&alternates_buf, alternates_path) < GIT_SUCCESS) - return git__throw(GIT_EOSERR, "Failed to add backend. Can't read alternates"); + if (git_futils_readbuffer(&alternates_buf, alternates_path.ptr) < 0) { + git_buf_free(&alternates_path); + return -1; + } - buffer = (char *)alternates_buf.data; - error = GIT_SUCCESS; + buffer = (char *)alternates_buf.ptr; /* add each alternate as a new backend; one alternate per line */ while ((alternate = git__strtok(&buffer, "\r\n")) != NULL) { - char full_path[GIT_PATH_MAX]; - if (*alternate == '\0' || *alternate == '#') continue; /* relative path: build based on the current `objects` folder */ if (*alternate == '.') { - git__joinpath(full_path, objects_dir, alternate); - alternate = full_path; + 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; } - gitfo_free_buf(&alternates_buf); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to load alternates"); - return error; + git_buf_free(&alternates_path); + git_buf_free(&alternates_buf); + + 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_close(db); - return error; /* error already set - pass through */ + return 0; } -void git_odb_close(git_odb *db) +static void odb_free(git_odb *db) { unsigned int i; - if (db == NULL) - return; - for (i = 0; i < db->backends.length; ++i) { backend_internal *internal = git_vector_get(&db->backends, i); git_odb_backend *backend = internal->backend; if (backend->free) backend->free(backend); - else free(backend); + else git__free(backend); - free(internal); + git__free(internal); } git_vector_free(&db->backends); git_cache_free(&db->cache); - free(db); + git__free(db); +} + +void git_odb_free(git_odb *db) +{ + if (db == NULL) + return; + + GIT_REFCOUNT_DEC(db, odb_free); } 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_close(object); - return 1; + git_odb_object_free(object); + return (int)true; } for (i = 0; i < db->backends.length && !found; ++i) { @@ -429,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) @@ -443,8 +493,8 @@ int git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *db, const git if ((object = git_cache_get(&db->cache, id)) != NULL) { *len_p = object->raw.len; *type_p = object->raw.type; - git_odb_object_close(object); - return GIT_SUCCESS; + git_odb_object_free(object); + return 0; } for (i = 0; i < db->backends.length && error < 0; ++i) { @@ -455,20 +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 || error == GIT_PASSTHROUGH) + 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 */ - - *len_p = object->raw.len; - *type_p = object->raw.type; - git_odb_object_close(object); - } + if ((error = git_odb_read(&object, db, id)) < 0) + return error; /* error already set - pass along */ - 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) @@ -481,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); @@ -491,27 +541,30 @@ 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_SUCCESS) { - *out = git_cache_try_store(&db->cache, new_odb_object(id, &raw)); - } + /* TODO: If no backends are configured, this returns GIT_ENOTFOUND but + * will never have called giterr_set(). + */ - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to read object"); - return error; + if (error && error != GIT_PASSTHROUGH) + 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; - git_oid full_oid; + git_oid found_full_oid = {{0}}; git_rawobj raw; - int found = 0; + bool found = false; 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; @@ -519,44 +572,42 @@ 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) { + for (i = 0; i < db->backends.length; ++i) { backend_internal *internal = git_vector_get(&db->backends, i); git_odb_backend *b = internal->backend; if (b->read != NULL) { + git_oid full_oid; error = b->read_prefix(&full_oid, &raw.data, &raw.len, &raw.type, b, short_id, len); - switch (error) { - case GIT_SUCCESS: - found++; - break; - case GIT_ENOTFOUND: - break; - case GIT_EAMBIGUOUSOIDPREFIX: - return git__rethrow(error, "Failed to read object. Ambiguous sha1 prefix"); - default: - return git__rethrow(error, "Failed to read object"); - } + if (error == GIT_ENOTFOUND || error == GIT_PASSTHROUGH) + continue; + + if (error) + return error; + + if (found && git_oid_cmp(&full_oid, &found_full_oid)) + return git_odb__error_ambiguous("multiple matches for prefix"); + found_full_oid = full_oid; + found = true; } } - 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) + return git_odb__error_notfound("no match for prefix", short_id); - return GIT_SUCCESS; + *out = git_cache_try_store(&db->cache, new_odb_object(&found_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; + git_odb_stream *stream; assert(oid && db); @@ -572,24 +623,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 || error == GIT_PASSTHROUGH) + 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_SUCCESS) { - git_odb_stream *stream; - 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); - } - } + if ((error = git_odb_open_wstream(&stream, db, len, type)) != 0) + return error; - return (error == GIT_SUCCESS) ? GIT_SUCCESS : - git__rethrow(error, "Failed to write object"); + stream->write(stream, data, len); + error = stream->finalize_write(oid, stream); + stream->free(stream); + + 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; @@ -610,11 +662,13 @@ int git_odb_open_wstream(git_odb_stream **stream, git_odb *db, size_t size, git_ error = init_fake_wstream(stream, b, size, type); } - return (error == GIT_SUCCESS) ? GIT_SUCCESS : - git__rethrow(error, "Failed to open write stream"); + if (error == GIT_PASSTHROUGH) + error = 0; + + return error; } -int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oid) +int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oid) { unsigned int i; int error = GIT_ERROR; @@ -629,7 +683,27 @@ int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oi error = b->readstream(stream, b, oid); } - return (error == GIT_SUCCESS) ? GIT_SUCCESS : - git__rethrow(error, "Failed to open read stream"); + if (error == GIT_PASSTHROUGH) + error = 0; + + return error; +} + +int git_odb__error_notfound(const char *message, const git_oid *oid) +{ + if (oid != NULL) { + char oid_str[GIT_OID_HEXSZ + 1]; + git_oid_tostr(oid_str, sizeof(oid_str), oid); + giterr_set(GITERR_ODB, "Object not found - %s (%s)", message, oid_str); + } else + 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; } @@ -1,3 +1,9 @@ +/* + * 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_odb_h__ #define INCLUDE_odb_h__ @@ -7,12 +13,17 @@ #include "vector.h" #include "cache.h" +#include "posix.h" + +#define GIT_OBJECTS_DIR "objects/" +#define GIT_OBJECT_DIR_MODE 0777 +#define GIT_OBJECT_FILE_MODE 0444 /* DO NOT EXPORT */ typedef struct { - void *data; /**< Raw, decompressed object data. */ - size_t len; /**< Total number of bytes in data. */ - git_otype type; /**< Type of this object. */ + void *data; /**< Raw, decompressed object data. */ + size_t len; /**< Total number of bytes in data. */ + git_otype type; /**< Type of this object. */ } git_rawobj; /* EXPORT */ @@ -23,11 +34,47 @@ struct git_odb_object { /* EXPORT */ struct git_odb { - void *_internal; + git_refcount rc; git_vector backends; git_cache cache; }; -int git_odb__hash_obj(git_oid *id, char *hdr, size_t n, int *len, git_rawobj *obj); +/* + * Hash a git_rawobj internally. + * The `git_rawobj` is supposed to be previously initialized + */ +int git_odb__hashobj(git_oid *id, git_rawobj *obj); + +/* + * Hash an open file descriptor. + * This is a performance call when the contents of a fd need to be hashed, + * but the fd is already open and we have the size of the contents. + * + * Saves us some `stat` calls. + * + * The fd is never closed, not even on error. It must be opened and closed + * by the caller + */ +int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type); + +/* + * Hash a `path`, assuming it could be a POSIX symlink: if the path is a symlink, + * then the raw contents of the symlink will be hashed. Otherwise, this will + * fallback to `git_odb__hashfd`. + * + * The hash type for this call is always `GIT_OBJ_BLOB` because symlinks may only + * point to blobs. + */ +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, const git_oid *oid); + +/* + * 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 deff59ad0..989b03ab2 100644 --- a/src/odb_loose.c +++ b/src/odb_loose.c @@ -1,50 +1,31 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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/zlib.h" +#include <zlib.h> #include "git2/object.h" +#include "git2/oid.h" #include "fileops.h" #include "hash.h" #include "odb.h" -#include "oid.h" #include "delta-apply.h" #include "filebuf.h" #include "git2/odb_backend.h" #include "git2/types.h" -typedef struct { /* object header data */ - git_otype type; /* object type */ - size_t size; /* object size */ +typedef struct { /* object header data */ + git_otype type; /* object type */ + size_t size; /* object size */ } obj_hdr; typedef struct { git_odb_stream stream; git_filebuf fbuf; - int finished; } loose_writestream; typedef struct loose_backend { @@ -69,41 +50,38 @@ typedef struct { } loose_locate_object_state; - /*********************************************************** * * MISCELANEOUS HELPER FUNCTIONS * ***********************************************************/ -static size_t object_file_name(char *name, size_t n, char *dir, const git_oid *id) +static int object_file_name(git_buf *name, const char *dir, const git_oid *id) { - size_t len = strlen(dir); + git_buf_sets(name, dir); - /* check length: 43 = 40 hex sha1 chars + 2 * '/' + '\0' */ - if (len+43 > n) - return len+43; + /* expand length for 40 hex sha1 chars + 2 * '/' + '\0' */ + if (git_buf_grow(name, git_buf_len(name) + GIT_OID_HEXSZ + 3) < 0) + return -1; - /* the object dir: eg $GIT_DIR/objects */ - strcpy(name, dir); - if (name[len-1] != '/') - name[len++] = '/'; + git_path_to_dir(name); /* loose object filename: aa/aaa... (41 bytes) */ - git_oid_pathfmt(&name[len], id); - name[len+41] = '\0'; + git_oid_pathfmt(name->ptr + git_buf_len(name), id); + name->size += GIT_OID_HEXSZ + 1; + name->ptr[name->size] = '\0'; return 0; } -static size_t get_binary_object_header(obj_hdr *hdr, gitfo_buf *obj) +static size_t get_binary_object_header(obj_hdr *hdr, git_buf *obj) { unsigned char c; - unsigned char *data = obj->data; + unsigned char *data = (unsigned char *)obj->ptr; size_t shift, size, used = 0; - if (obj->len == 0) + if (git_buf_len(obj) == 0) return 0; c = data[used++]; @@ -112,7 +90,7 @@ static size_t get_binary_object_header(obj_hdr *hdr, gitfo_buf *obj) size = c & 15; shift = 4; while (c & 0x80) { - if (obj->len <= used) + if (git_buf_len(obj) <= used) return 0; if (sizeof(size_t) * 8 <= shift) return 0; @@ -142,7 +120,7 @@ static size_t get_object_header(obj_hdr *hdr, unsigned char *data) if (used == 0) return 0; hdr->type = git_object_string2type(typename); - used++; /* consume the space */ + used++; /* consume the space */ /* * length follows immediately in decimal (without @@ -182,29 +160,29 @@ static size_t get_object_header(obj_hdr *hdr, unsigned char *data) static void init_stream(z_stream *s, void *out, size_t len) { memset(s, 0, sizeof(*s)); - s->next_out = out; - s->avail_out = len; + s->next_out = out; + s->avail_out = (uInt)len; } static void set_stream_input(z_stream *s, void *in, size_t len) { - s->next_in = in; - s->avail_in = len; + s->next_in = in; + s->avail_in = (uInt)len; } static void set_stream_output(z_stream *s, void *out, size_t len) { - s->next_out = out; - s->avail_out = len; + s->next_out = out; + s->avail_out = (uInt)len; } -static int start_inflate(z_stream *s, gitfo_buf *obj, void *out, size_t len) +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->data, obj->len); + set_stream_input(s, obj->ptr, git_buf_len(obj)); if ((status = inflateInit(s)) < Z_OK) return status; @@ -221,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) @@ -232,7 +212,7 @@ static int is_zlib_compressed_data(unsigned char *data) unsigned int w; w = ((unsigned int)(data[0]) << 8) + data[1]; - return data[0] == 0x78 && !(w % 31); + return (data[0] & 0x8F) == 0x08 && !(w % 31); } static int inflate_buffer(void *in, size_t inlen, void *out, size_t outlen) @@ -242,27 +222,30 @@ static int inflate_buffer(void *in, size_t inlen, void *out, size_t outlen) memset(&zs, 0x0, sizeof(zs)); - zs.next_out = out; - zs.avail_out = outlen; + zs.next_out = out; + zs.avail_out = (uInt)outlen; - zs.next_in = in; - zs.avail_in = inlen; + 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) @@ -295,7 +278,7 @@ static void *inflate_tail(z_stream *s, void *hb, size_t used, obj_hdr *hdr) else { set_stream_output(s, buf + used, hdr->size - used); if (finish_inflate(s)) { - free(buf); + git__free(buf); return NULL; } } @@ -309,7 +292,7 @@ static void *inflate_tail(z_stream *s, void *hb, size_t used, obj_hdr *hdr) * of loose object data into packs. This format is no longer used, but * we must still read it. */ -static int inflate_packlike_loose_disk_obj(git_rawobj *out, gitfo_buf *obj) +static int inflate_packlike_loose_disk_obj(git_rawobj *out, git_buf *obj) { unsigned char *in, *buf; obj_hdr hdr; @@ -319,35 +302,34 @@ static int inflate_packlike_loose_disk_obj(git_rawobj *out, gitfo_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; - - in = ((unsigned char *)obj->data) + used; - len = obj->len - used; - if (inflate_buffer(in, len, buf, hdr.size)) { - free(buf); - return git__throw(GIT_ERROR, "Failed to inflate loose object. Could not inflate buffer"); + GITERR_CHECK_ALLOC(buf); + + in = ((unsigned char *)obj->ptr) + used; + len = obj->size - used; + if (inflate_buffer(in, len, buf, hdr.size) < 0) { + git__free(buf); + return -1; } buf[hdr.size] = '\0'; out->data = buf; - out->len = hdr.size; + out->len = hdr.size; out->type = hdr.type; - return GIT_SUCCESS; + return 0; } -static int inflate_disk_obj(git_rawobj *out, gitfo_buf *obj) +static int inflate_disk_obj(git_rawobj *out, git_buf *obj) { unsigned char head[64], *buf; z_stream zs; @@ -357,35 +339,34 @@ static int inflate_disk_obj(git_rawobj *out, gitfo_buf *obj) /* * check for a pack-like loose object */ - if (!is_zlib_compressed_data(obj->data)) + if (!is_zlib_compressed_data((unsigned char *)obj->ptr)) return inflate_packlike_loose_disk_obj(out, 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->len = hdr.size; out->type = hdr.type; - return GIT_SUCCESS; + return 0; } @@ -402,29 +383,31 @@ static int inflate_disk_obj(git_rawobj *out, gitfo_buf *obj) * ***********************************************************/ -static int read_loose(git_rawobj *out, const char *loc) +static int read_loose(git_rawobj *out, git_buf *loc) { int error; - gitfo_buf obj = GITFO_BUF_INIT; + git_buf obj = GIT_BUF_INIT; assert(out && loc); + if (git_buf_oom(loc)) + return -1; + out->data = NULL; - out->len = 0; + out->len = 0; out->type = GIT_OBJ_BAD; - if (gitfo_read_file(&obj, loc) < 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); - gitfo_free_buf(&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, const char *loc) +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; @@ -432,129 +415,151 @@ static int read_header_loose(git_rawobj *out, const char *loc) assert(out && loc); + if (git_buf_oom(loc)) + return -1; + out->data = NULL; - if ((fd = gitfo_open(loc, 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); - gitfo_close(fd); + 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(char *object_location, loose_backend *backend, const git_oid *oid) +static int locate_object( + git_buf *object_location, + loose_backend *backend, + const git_oid *oid) { - object_file_name(object_location, GIT_PATH_MAX, backend->objects_dir, oid); - return gitfo_exists(object_location); + int error = object_file_name(object_location, backend->objects_dir, oid); + + if (!error && !git_path_exists(object_location->ptr)) + return GIT_ENOTFOUND; + + return error; } /* Explore an entry of a directory and see if it matches a short oid */ -int fn_locate_object_short_oid(void *state, char *pathbuf) { +static int fn_locate_object_short_oid(void *state, git_buf *pathbuf) { loose_locate_object_state *sstate = (loose_locate_object_state *)state; - size_t pathbuf_len = strlen(pathbuf); - if (pathbuf_len - 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 (!gitfo_exists(pathbuf) && gitfo_isdir(pathbuf)) { - /* We are already in the directory matching the 2 first hex characters */ - if (!git_oid_match_hex(sstate->short_oid_len-2, sstate->short_oid+2, (unsigned char *)pathbuf + sstate->dir_len)) { + 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, + (unsigned char *)pathbuf->ptr + sstate->dir_len, + sstate->short_oid_len - 2)) { + if (!sstate->found) { sstate->res_oid[0] = sstate->short_oid[0]; sstate->res_oid[1] = sstate->short_oid[1]; - memcpy(sstate->res_oid+2, pathbuf+sstate->dir_len, GIT_OID_HEXSZ-2); + memcpy(sstate->res_oid+2, pathbuf->ptr+sstate->dir_len, GIT_OID_HEXSZ-2); } 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 */ -static int locate_object_short_oid(char *object_location, git_oid *res_oid, loose_backend *backend, const git_oid *short_oid, unsigned int len) +static int locate_object_short_oid( + git_buf *object_location, + git_oid *res_oid, + loose_backend *backend, + const git_oid *short_oid, + unsigned int len) { char *objects_dir = backend->objects_dir; size_t dir_len = strlen(objects_dir); loose_locate_object_state state; int error; - if (dir_len+43 > GIT_PATH_MAX) - return git__throw(GIT_ERROR, "Failed to locate object from short oid. Object path too long"); + /* prealloc memory for OBJ_DIR/xx/ */ + if (git_buf_grow(object_location, dir_len + 5) < 0) + return -1; - strcpy(object_location, objects_dir); + git_buf_sets(object_location, objects_dir); + git_path_to_dir(object_location); - /* Add a separator if not already there */ - if (object_location[dir_len-1] != '/') - object_location[dir_len++] = '/'; + /* save adjusted position at end of dir so it can be restored later */ + 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 */ - sprintf(object_location+dir_len, "%.2s/", state.short_oid); + if (git_buf_printf(object_location, "%.2s/", state.short_oid) < 0) + return -1; /* Check that directory exists */ - if (gitfo_exists(object_location) || gitfo_isdir(object_location)) - 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("no matching loose object for prefix", short_oid); - state.dir_len = dir_len+3; + 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 = gitfo_dirent(object_location, GIT_PATH_MAX, fn_locate_object_short_oid, &state); - if (error) { - return git__rethrow(error, "Failed to locate object from short oid"); - } - if (!state.found) { - return git__throw(GIT_ENOTFOUND, "Failed to locate object from short oid. Object not found"); - } + error = git_path_direach( + object_location, fn_locate_object_short_oid, &state); + if (error) + return error; + + if (!state.found) + return git_odb__error_notfound("no matching loose object for prefix", short_oid); /* Convert obtained hex formatted oid to raw */ - error = git_oid_mkstr(res_oid, (char *)state.res_oid); - if (error) { - return git__rethrow(error, "Failed to locate object from short oid"); - } + error = git_oid_fromstr(res_oid, (char *)state.res_oid); + if (error) + return error; /* Update the location according to the oid obtained */ - git_oid_pathfmt(object_location+dir_len, res_oid); - return GIT_SUCCESS; + git_buf_truncate(object_location, dir_len); + 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 0; } @@ -573,9 +578,9 @@ static int locate_object_short_oid(char *object_location, git_oid *res_oid, loos * ***********************************************************/ -int loose_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid) +static int loose_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid) { - char object_path[GIT_PATH_MAX]; + git_buf object_path = GIT_BUF_INIT; git_rawobj raw; int error; @@ -584,39 +589,40 @@ int loose_backend__read_header(size_t *len_p, git_otype *type_p, git_odb_backend raw.len = 0; raw.type = GIT_OBJ_BAD; - if (locate_object(object_path, (loose_backend *)backend, oid) < 0) - return git__throw(GIT_ENOTFOUND, "Failed to read loose backend header. Object not found"); + if (locate_object(&object_path, (loose_backend *)backend, oid) < 0) + error = git_odb__error_notfound("no matching loose object", oid); + else if ((error = read_header_loose(&raw, &object_path)) == 0) { + *len_p = raw.len; + *type_p = raw.type; + } - if ((error = read_header_loose(&raw, object_path)) < GIT_SUCCESS) - return error; + git_buf_free(&object_path); - *len_p = raw.len; - *type_p = raw.type; - return GIT_SUCCESS; + return error; } -int loose_backend__read(void **buffer_p, size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid) +static int loose_backend__read(void **buffer_p, size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid) { - char object_path[GIT_PATH_MAX]; + git_buf object_path = GIT_BUF_INIT; git_rawobj raw; - int error; + int error = 0; assert(backend && oid); - if (locate_object(object_path, (loose_backend *)backend, oid) < 0) - return git__throw(GIT_ENOTFOUND, "Failed to read loose backend. Object not found"); - - if ((error = read_loose(&raw, object_path)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to read loose backend"); + if (locate_object(&object_path, (loose_backend *)backend, oid) < 0) + error = git_odb__error_notfound("no matching loose object", oid); + else if ((error = read_loose(&raw, &object_path)) == 0) { + *buffer_p = raw.data; + *len_p = raw.len; + *type_p = raw.type; + } - *buffer_p = raw.data; - *len_p = raw.len; - *type_p = raw.type; + git_buf_free(&object_path); - return GIT_SUCCESS; + return error; } -int loose_backend__read_prefix( +static int loose_backend__read_prefix( git_oid *out_oid, void **buffer_p, size_t *len_p, @@ -625,82 +631,90 @@ int loose_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 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 */ - int error = loose_backend__read(buffer_p, len_p, type_p, backend, short_oid); - if (error == GIT_SUCCESS) + error = loose_backend__read(buffer_p, len_p, type_p, backend, short_oid); + if (!error) git_oid_cpy(out_oid, short_oid); - - return error; } else { - char object_path[GIT_PATH_MAX]; + git_buf object_path = GIT_BUF_INIT; git_rawobj raw; - int error; assert(backend && short_oid); - if ((error = locate_object_short_oid(object_path, out_oid, (loose_backend *)backend, short_oid, len)) < 0) { - return git__rethrow(error, "Failed to read loose backend"); + if ((error = locate_object_short_oid(&object_path, out_oid, + (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; } - if ((error = read_loose(&raw, object_path)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to read loose backend"); - - *buffer_p = raw.data; - *len_p = raw.len; - *type_p = raw.type; + git_buf_free(&object_path); } - return GIT_SUCCESS; + return error; } -int loose_backend__exists(git_odb_backend *backend, const git_oid *oid) +static int loose_backend__exists(git_odb_backend *backend, const git_oid *oid) { - char object_path[GIT_PATH_MAX]; + git_buf object_path = GIT_BUF_INIT; + int error; assert(backend && oid); - return locate_object(object_path, (loose_backend *)backend, oid) == GIT_SUCCESS; + error = locate_object(&object_path, (loose_backend *)backend, oid); + + git_buf_free(&object_path); + + return !error; } -int loose_backend__stream_fwrite(git_oid *oid, git_odb_stream *_stream) +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; + git_buf final_path = GIT_BUF_INIT; + int error = 0; - int error; - char final_path[GIT_PATH_MAX]; - - if ((error = git_filebuf_hash(oid, &stream->fbuf)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to write loose backend"); - - if (object_file_name(final_path, sizeof(final_path), backend->objects_dir, oid)) - return GIT_ENOMEM; + 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. + */ + else if (git_path_exists(final_path.ptr) == true) + git_filebuf_cleanup(&stream->fbuf); + else + error = git_filebuf_commit_at( + &stream->fbuf, final_path.ptr, GIT_OBJECT_FILE_MODE); - if ((error = gitfo_mkdir_2file(final_path)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to write loose backend"); + git_buf_free(&final_path); - stream->finished = 1; - return git_filebuf_commit_at(&stream->fbuf, final_path); + return error; } -int loose_backend__stream_write(git_odb_stream *_stream, const char *data, size_t len) +static int loose_backend__stream_write(git_odb_stream *_stream, const char *data, size_t len) { loose_writestream *stream = (loose_writestream *)_stream; return git_filebuf_write(&stream->fbuf, data, len); } -void loose_backend__stream_free(git_odb_stream *_stream) +static void loose_backend__stream_free(git_odb_stream *_stream) { loose_writestream *stream = (loose_writestream *)_stream; - if (!stream->finished) - git_filebuf_cleanup(&stream->fbuf); - - free(stream); + git_filebuf_cleanup(&stream->fbuf); + git__free(stream); } static int format_object_header(char *hdr, size_t n, size_t obj_len, git_otype obj_type) @@ -708,22 +722,19 @@ 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 = 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(len > 0); /* otherwise snprintf() 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; } -int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_backend, size_t length, git_otype type) +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; - - char hdr[64], tmp_path[GIT_PATH_MAX]; - int hdrlen; - int error; + loose_writestream *stream = NULL; + char hdr[64]; + git_buf tmp_path = GIT_BUF_INIT; + int hdrlen; assert(_backend); @@ -731,12 +742,9 @@ int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_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 */ @@ -745,57 +753,94 @@ int loose_backend__stream(git_odb_stream **stream_out, git_odb_backend *_backend stream->stream.free = &loose_backend__stream_free; stream->stream.mode = GIT_STREAM_WRONLY; - git__joinpath(tmp_path, backend->objects_dir, "tmp_object"); + 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; - error = git_filebuf_open(&stream->fbuf, tmp_path, - GIT_FILEBUF_HASH_CONTENTS | - GIT_FILEBUF_DEFLATE_CONTENTS | - GIT_FILEBUF_TEMPORARY); + return !stream ? -1 : 0; +} - if (error < GIT_SUCCESS) { - free(stream); - return git__rethrow(error, "Failed to create loose backend stream"); - } +static int loose_backend__write(git_oid *oid, git_odb_backend *_backend, const void *data, size_t len, git_otype type) +{ + int error = 0, header_len; + git_buf final_path = GIT_BUF_INIT; + char header[64]; + git_filebuf fbuf = GIT_FILEBUF_INIT; + loose_backend *backend; - error = stream->stream.write((git_odb_stream *)stream, hdr, hdrlen); - if (error < GIT_SUCCESS) { - git_filebuf_cleanup(&stream->fbuf); - free(stream); - return git__rethrow(error, "Failed to create loose backend stream"); + backend = (loose_backend *)_backend; + + /* prepare the header for the file */ + header_len = format_object_header(header, sizeof(header), len, type); + + 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; } - *stream_out = (git_odb_stream *)stream; - return GIT_SUCCESS; + git_filebuf_write(&fbuf, header, header_len); + git_filebuf_write(&fbuf, data, len); + git_filebuf_hash(oid, &fbuf); + + 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 < 0) + git_filebuf_cleanup(&fbuf); + git_buf_free(&final_path); + return error; } -void loose_backend__free(git_odb_backend *_backend) +static void loose_backend__free(git_odb_backend *_backend) { loose_backend *backend; assert(_backend); backend = (loose_backend *)_backend; - free(backend->objects_dir); - free(backend); + git__free(backend->objects_dir); + git__free(backend); } -int git_odb_backend_loose(git_odb_backend **backend_out, const char *objects_dir) +int git_odb_backend_loose( + git_odb_backend **backend_out, + const char *objects_dir, + int compression_level, + int do_fsync) { 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) { - free(backend); - return GIT_ENOMEM; - } + GITERR_CHECK_ALLOC(backend->objects_dir); + + if (compression_level < 0) + compression_level = Z_BEST_SPEED; - backend->object_zlib_level = Z_BEST_SPEED; - backend->fsync_object_files = 0; + backend->object_zlib_level = compression_level; + backend->fsync_object_files = do_fsync; backend->parent.read = &loose_backend__read; + backend->parent.write = &loose_backend__write; backend->parent.read_prefix = &loose_backend__read_prefix; backend->parent.read_header = &loose_backend__read_header; backend->parent.writestream = &loose_backend__stream; @@ -803,5 +848,5 @@ int git_odb_backend_loose(git_odb_backend **backend_out, const char *objects_dir 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 525cfa429..458f288d9 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -1,142 +1,38 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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/zlib.h" +#include <zlib.h> #include "git2/repository.h" +#include "git2/oid.h" #include "fileops.h" #include "hash.h" #include "odb.h" -#include "oid.h" #include "delta-apply.h" #include "sha1_lookup.h" +#include "mwindow.h" +#include "pack.h" #include "git2/odb_backend.h" -#define DEFAULT_WINDOW_SIZE \ - (sizeof(void*) >= 8 \ - ? 1 * 1024 * 1024 * 1024 \ - : 32 * 1024 * 1024) - -#define DEFAULT_MAPPED_LIMIT \ - ((1024L * 1024L) * (sizeof(void*) >= 8 ? 8192 : 256)) - -#define PACK_SIGNATURE 0x5041434b /* "PACK" */ -#define PACK_VERSION 2 -#define pack_version_ok(v) ((v) == htonl(2) || (v) == htonl(3)) -struct pack_header { - uint32_t hdr_signature; - uint32_t hdr_version; - uint32_t hdr_entries; -}; - -/* - * The first four bytes of index formats later than version 1 should - * start with this signature, as all older git binaries would find this - * value illegal and abort reading the file. - * - * This is the case because the number of objects in a packfile - * cannot exceed 1,431,660,000 as every object would need at least - * 3 bytes of data and the overall packfile cannot exceed 4 GiB with - * version 1 of the index file due to the offsets limited to 32 bits. - * Clearly the signature exceeds this maximum. - * - * Very old git binaries will also compare the first 4 bytes to the - * next 4 bytes in the index and abort with a "non-monotonic index" - * error if the second 4 byte word is smaller than the first 4 - * byte word. This would be true in the proposed future index - * format as idx_signature would be greater than idx_version. - */ -#define PACK_IDX_SIGNATURE 0xff744f63 /* "\377tOc" */ - -struct pack_idx_header { - uint32_t idx_signature; - uint32_t idx_version; -}; - -struct pack_window { - struct pack_window *next; - git_map window_map; - off_t offset; - unsigned int last_used; - unsigned int inuse_cnt; -}; - -struct pack_file { - struct pack_window *windows; - off_t pack_size; - - git_map index_map; - - uint32_t num_objects; - uint32_t num_bad_objects; - git_oid *bad_object_sha1; /* array of git_oid */ - - int index_version; - git_time_t mtime; - int pack_fd; - unsigned pack_local:1, pack_keep:1; - git_oid sha1; - - /* something like ".git/objects/pack/xxxxx.pack" */ - char pack_name[GIT_FLEX_ARRAY]; /* more */ -}; - -struct pack_entry { - off_t offset; - git_oid sha1; - struct pack_file *p; -}; - struct pack_backend { git_odb_backend parent; git_vector packs; - struct pack_file *last_found; + struct git_pack_file *last_found; char *pack_folder; time_t pack_folder_mtime; - - size_t window_size; /* needs default value */ - - size_t mapped_limit; /* needs default value */ - size_t peak_mapped; - size_t mapped; - - size_t used_ctr; - - unsigned int peak_open_windows; - unsigned int open_windows; - - unsigned int mmap_calls; }; /** * The wonderful tale of a Packed Object lookup query * =================================================== - * A riveting and epic story of epicness and ASCII - * art, presented by yours truly, - * Sir Vicent of Marti + * A riveting and epic story of epicness and ASCII + * art, presented by yours truly, + * Sir Vicent of Marti * * * Chapter 1: Once upon a time... @@ -147,32 +43,32 @@ struct pack_backend { * | Creates the pack backend structure, initializes the * | callback pointers to our default read() and exist() methods, * | and tries to preload all the known packfiles in the ODB. - * | + * | * |-# packfile_load_all - * | Tries to find the `pack` folder, if it exists. ODBs without - * | a pack folder are ignored altogether. If there's a `pack` folder - * | we run a `dirent` callback through every file in the pack folder - * | to find our packfiles. The packfiles are then sorted according - * | to a sorting callback. - * | - * |-# packfile_load__cb - * | | This callback is called from `dirent` with every single file - * | | inside the pack folder. We find the packs by actually locating - * | | their index (ends in ".idx"). From that index, we verify that - * | | the corresponding packfile exists and is valid, and if so, we - * | | add it to the pack list. - * | | - * | |-# packfile_check - * | Make sure that there's a packfile to back this index, and store - * | some very basic information regarding the packfile itself, - * | such as the full path, the size, and the modification time. - * | We don't actually open the packfile to check for internal consistency. - * | - * |-# packfile_sort__cb - * Sort all the preloaded packs according to some specific criteria: - * we prioritize the "newer" packs because it's more likely they - * contain the objects we are looking for, and we prioritize local - * packs over remote ones. + * | Tries to find the `pack` folder, if it exists. ODBs without + * | a pack folder are ignored altogether. If there's a `pack` folder + * | we run a `dirent` callback through every file in the pack folder + * | to find our packfiles. The packfiles are then sorted according + * | to a sorting callback. + * | + * |-# packfile_load__cb + * | | This callback is called from `dirent` with every single file + * | | inside the pack folder. We find the packs by actually locating + * | | their index (ends in ".idx"). From that index, we verify that + * | | the corresponding packfile exists and is valid, and if so, we + * | | add it to the pack list. + * | | + * | |-# packfile_check + * | Make sure that there's a packfile to back this index, and store + * | some very basic information regarding the packfile itself, + * | such as the full path, the size, and the modification time. + * | We don't actually open the packfile to check for internal consistency. + * | + * |-# packfile_sort__cb + * Sort all the preloaded packs according to some specific criteria: + * we prioritize the "newer" packs because it's more likely they + * contain the objects we are looking for, and we prioritize local + * packs over remote ones. * * * @@ -180,41 +76,41 @@ struct pack_backend { * A standard packed `exist` query for an OID * -------------------------------------------------- * - * # pack_backend__exists - * | Check if the given SHA1 oid exists in any of the packs - * | that have been loaded for our ODB. - * | - * |-# pack_entry_find - * | Iterate through all the packs that have been preloaded - * | (starting by the pack where the latest object was found) - * | to try to find the OID in one of them. - * | - * |-# pack_entry_find1 - * | Check the index of an individual pack to see if the SHA1 - * | OID can be found. If we can find the offset to that SHA1 - * | inside of the index, that means the object is contained - * | inside of the packfile and we can stop searching. - * | Before returning, we verify that the packfile behing the - * | index we are searching still exists on disk. - * | - * |-# pack_entry_find_offset - * | | Mmap the actual index file to disk if it hasn't been opened - * | | yet, and run a binary search through it to find the OID. - * | | See <http://book.git-scm.com/7_the_packfile.html> for specifics - * | | on the Packfile Index format and how do we find entries in it. - * | | - * | |-# pack_index_open - * | | Guess the name of the index based on the full path to the - * | | packfile, open it and verify its contents. Only if the index - * | | has not been opened already. - * | | - * | |-# pack_index_check - * | Mmap the index file and do a quick run through the header - * | to guess the index version (right now we support v1 and v2), - * | and to verify that the size of the index makes sense. - * | - * |-# packfile_open - * See `packfile_open` in Chapter 3 + * # pack_backend__exists + * | Check if the given SHA1 oid exists in any of the packs + * | that have been loaded for our ODB. + * | + * |-# pack_entry_find + * | Iterate through all the packs that have been preloaded + * | (starting by the pack where the latest object was found) + * | to try to find the OID in one of them. + * | + * |-# pack_entry_find1 + * | Check the index of an individual pack to see if the SHA1 + * | OID can be found. If we can find the offset to that SHA1 + * | inside of the index, that means the object is contained + * | inside of the packfile and we can stop searching. + * | Before returning, we verify that the packfile behing the + * | index we are searching still exists on disk. + * | + * |-# pack_entry_find_offset + * | | Mmap the actual index file to disk if it hasn't been opened + * | | yet, and run a binary search through it to find the OID. + * | | See <http://book.git-scm.com/7_the_packfile.html> for specifics + * | | on the Packfile Index format and how do we find entries in it. + * | | + * | |-# pack_index_open + * | | Guess the name of the index based on the full path to the + * | | packfile, open it and verify its contents. Only if the index + * | | has not been opened already. + * | | + * | |-# pack_index_check + * | Mmap the index file and do a quick run through the header + * | to guess the index version (right now we support v1 and v2), + * | and to verify that the size of the index makes sense. + * | + * |-# packfile_open + * See `packfile_open` in Chapter 3 * * * @@ -226,121 +122,34 @@ struct pack_backend { */ - - /*********************************************************** * * FORWARD DECLARATIONS * ***********************************************************/ -static void pack_window_free_all(struct pack_backend *backend, struct pack_file *p); -static int pack_window_contains(struct pack_window *win, off_t offset); - -static void pack_window_scan_lru(struct pack_file *p, struct pack_file **lru_p, - struct pack_window **lru_w, struct pack_window **lru_l); - -static int pack_window_close_lru( struct pack_backend *backend, - struct pack_file *current, git_file keep_fd); - -static void pack_window_close(struct pack_window **w_cursor); - -static unsigned char *pack_window_open( struct pack_backend *backend, - struct pack_file *p, struct pack_window **w_cursor, off_t offset, - unsigned int *left); +static void pack_window_free_all(struct pack_backend *backend, struct git_pack_file *p); +static int pack_window_contains(git_mwindow *win, off_t offset); static int packfile_sort__cb(const void *a_, const void *b_); -static void pack_index_free(struct pack_file *p); - -static int pack_index_check(const char *path, struct pack_file *p); -static int pack_index_open(struct pack_file *p); - -static struct pack_file *packfile_alloc(int extra); -static int packfile_open(struct pack_file *p); -static int packfile_check(struct pack_file **pack_out, const char *path); -static int packfile_load__cb(void *_data, char *path); +static int packfile_load__cb(void *_data, git_buf *path); static int packfile_refresh_all(struct pack_backend *backend); -static off_t nth_packed_object_offset(const struct pack_file *p, uint32_t n); +static int pack_entry_find(struct git_pack_entry *e, + 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 within the pack. + * 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_offset( - off_t *offset_out, - git_oid *found_oid, - struct pack_file *p, - const git_oid *short_oid, - unsigned int len); - -static int pack_entry_find1( - struct pack_entry *e, - struct pack_file *p, - const git_oid *short_oid, - unsigned int len); - -static int pack_entry_find(struct pack_entry *e, - 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. - * This method assumes that len is between - * GIT_OID_MINPREFIXLEN and GIT_OID_HEXSZ. - */ -static int pack_entry_find_prefix(struct pack_entry *e, - struct pack_backend *backend, - const git_oid *short_oid, - unsigned int len); - -static off_t get_delta_base(struct pack_backend *backend, - struct pack_file *p, struct pack_window **w_curs, - off_t *curpos, git_otype type, - off_t delta_obj_offset); - -static unsigned long packfile_unpack_header1( - size_t *sizep, - git_otype *type, - const unsigned char *buf, - unsigned long len); - -static int packfile_unpack_header( - size_t *size_p, - git_otype *type_p, - struct pack_backend *backend, - struct pack_file *p, - struct pack_window **w_curs, - off_t *curpos); - -static int packfile_unpack_compressed( - git_rawobj *obj, - struct pack_backend *backend, - struct pack_file *p, - struct pack_window **w_curs, - off_t curpos, - size_t size, - git_otype type); - -static int packfile_unpack_delta( - git_rawobj *obj, - struct pack_backend *backend, - struct pack_file *p, - struct pack_window **w_curs, - off_t curpos, - size_t delta_size, - git_otype delta_type, - off_t obj_offset); - -static int packfile_unpack(git_rawobj *obj, struct pack_backend *backend, - struct pack_file *p, off_t obj_offset); - - +static int pack_entry_find_prefix( + struct git_pack_entry *e, + struct pack_backend *backend, + const git_oid *short_oid, + unsigned int len); @@ -350,357 +159,32 @@ static int packfile_unpack(git_rawobj *obj, struct pack_backend *backend, * ***********************************************************/ -void pack_window_free_all(struct pack_backend *backend, struct pack_file *p) +GIT_INLINE(void) pack_window_free_all(struct pack_backend *backend, struct git_pack_file *p) { - while (p->windows) { - struct pack_window *w = p->windows; - assert(w->inuse_cnt == 0); - - backend->mapped -= w->window_map.len; - backend->open_windows--; - - gitfo_free_map(&w->window_map); - - p->windows = w->next; - free(w); - } + GIT_UNUSED(backend); + git_mwindow_free_all(&p->mwf); } -GIT_INLINE(int) pack_window_contains(struct pack_window *win, off_t offset) +GIT_INLINE(int) pack_window_contains(git_mwindow *win, off_t offset) { /* We must promise at least 20 bytes (one hash) after the * offset is available from this window, otherwise the offset * is not actually in this window and a different window (which - * has that one hash excess) must be used. This is to support + * has that one hash excess) must be used. This is to support * the object header and delta base parsing routines below. */ - off_t win_off = win->offset; - return win_off <= offset - && (offset + 20) <= (off_t)(win_off + win->window_map.len); -} - -static void pack_window_scan_lru( - struct pack_file *p, - struct pack_file **lru_p, - struct pack_window **lru_w, - struct pack_window **lru_l) -{ - struct pack_window *w, *w_l; - - for (w_l = NULL, w = p->windows; w; w = w->next) { - if (!w->inuse_cnt) { - if (!*lru_w || w->last_used < (*lru_w)->last_used) { - *lru_p = p; - *lru_w = w; - *lru_l = w_l; - } - } - w_l = w; - } -} - -static int pack_window_close_lru( - struct pack_backend *backend, - struct pack_file *current, - git_file keep_fd) -{ - struct pack_file *lru_p = NULL; - struct pack_window *lru_w = NULL, *lru_l = NULL; - size_t i; - - if (current) - pack_window_scan_lru(current, &lru_p, &lru_w, &lru_l); - - for (i = 0; i < backend->packs.length; ++i) - pack_window_scan_lru(git_vector_get(&backend->packs, i), &lru_p, &lru_w, &lru_l); - - if (lru_p) { - backend->mapped -= lru_w->window_map.len; - gitfo_free_map(&lru_w->window_map); - - if (lru_l) - lru_l->next = lru_w->next; - else { - lru_p->windows = lru_w->next; - if (!lru_p->windows && lru_p->pack_fd != keep_fd) { - gitfo_close(lru_p->pack_fd); - lru_p->pack_fd = -1; - } - } - - free(lru_w); - backend->open_windows--; - return GIT_SUCCESS; - } - - return git__throw(GIT_ERROR, "Failed to close pack window"); -} - -static void pack_window_close(struct pack_window **w_cursor) -{ - struct pack_window *w = *w_cursor; - if (w) { - w->inuse_cnt--; - *w_cursor = NULL; - } -} - -static unsigned char *pack_window_open( - struct pack_backend *backend, - struct pack_file *p, - struct pack_window **w_cursor, - off_t offset, - unsigned int *left) -{ - struct pack_window *win = *w_cursor; - - if (p->pack_fd == -1 && packfile_open(p) < GIT_SUCCESS) - return NULL; - - /* Since packfiles end in a hash of their content and it's - * pointless to ask for an offset into the middle of that - * hash, and the pack_window_contains function above wouldn't match - * don't allow an offset too close to the end of the file. - */ - if (offset > (p->pack_size - 20)) - return NULL; - - if (!win || !pack_window_contains(win, offset)) { - - if (win) - win->inuse_cnt--; - - for (win = p->windows; win; win = win->next) { - if (pack_window_contains(win, offset)) - break; - } - - if (!win) { - size_t window_align = backend->window_size / 2; - size_t len; - - win = git__calloc(1, sizeof(*win)); - win->offset = (offset / window_align) * window_align; - - len = (size_t)(p->pack_size - win->offset); - if (len > backend->window_size) - len = backend->window_size; - - backend->mapped += len; - - while (backend->mapped_limit < backend->mapped && - pack_window_close_lru(backend, p, p->pack_fd) == GIT_SUCCESS) {} - - if (gitfo_map_ro(&win->window_map, p->pack_fd, - win->offset, len) < GIT_SUCCESS) - return NULL; - - backend->mmap_calls++; - backend->open_windows++; - - if (backend->mapped > backend->peak_mapped) - backend->peak_mapped = backend->mapped; - - if (backend->open_windows > backend->peak_open_windows) - backend->peak_open_windows = backend->open_windows; - - win->next = p->windows; - p->windows = win; - } - } - - if (win != *w_cursor) { - win->last_used = backend->used_ctr++; - win->inuse_cnt++; - *w_cursor = win; - } - - offset -= win->offset; - assert(git__is_sizet(offset)); - - if (left) - *left = win->window_map.len - (size_t)offset; - - return (unsigned char *)win->window_map.data + offset; + return git_mwindow_contains(win, offset + 20); } - - - - - - -/*********************************************************** - * - * PACK INDEX METHODS - * - ***********************************************************/ - -static void pack_index_free(struct pack_file *p) -{ - if (p->index_map.data) { - gitfo_free_map(&p->index_map); - p->index_map.data = NULL; - } -} - -static int pack_index_check(const char *path, struct pack_file *p) -{ - struct 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 = gitfo_open(path, O_RDONLY /*| O_NOATIME */); - - int error; - - if (fd < 0) - return git__throw(GIT_EOSERR, "Failed to check index. File missing or corrupted"); - - if (gitfo_fstat(fd, &st) < GIT_SUCCESS) { - gitfo_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) { - gitfo_close(fd); - return git__throw(GIT_EOBJCORRUPTED, "Failed to check index. Object is corrupted"); - } - - error = gitfo_map_ro(&p->index_map, fd, 0, idx_size); - gitfo_close(fd); - - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to check index"); - - hdr = idx_map = p->index_map.data; - - if (hdr->idx_signature == htonl(PACK_IDX_SIGNATURE)) { - version = ntohl(hdr->idx_version); - - if (version < 2 || version > 2) { - gitfo_free_map(&p->index_map); - return git__throw(GIT_EOBJCORRUPTED, "Failed to check index. Unsupported index version"); - } - - } else - version = 1; - - nr = 0; - index = idx_map; - - if (version > 1) - index += 2; /* skip index header */ - - for (i = 0; i < 256; i++) { - uint32_t n = ntohl(index[i]); - if (n < nr) { - gitfo_free_map(&p->index_map); - return git__throw(GIT_EOBJCORRUPTED, "Failed to check index. Index is non-monotonic"); - } - nr = n; - } - - if (version == 1) { - /* - * Total size: - * - 256 index entries 4 bytes each - * - 24-byte entries * nr (20-byte sha1 + 4-byte offset) - * - 20-byte SHA1 of the packfile - * - 20-byte SHA1 file checksum - */ - if (idx_size != 4*256 + nr * 24 + 20 + 20) { - gitfo_free_map(&p->index_map); - return git__throw(GIT_EOBJCORRUPTED, "Failed to check index. Object is corrupted"); - } - } else if (version == 2) { - /* - * Minimum size: - * - 8 bytes of header - * - 256 index entries 4 bytes each - * - 20-byte sha1 entry * nr - * - 4-byte crc entry * nr - * - 4-byte offset entry * nr - * - 20-byte SHA1 of the packfile - * - 20-byte SHA1 file checksum - * And after the 4-byte offset table might be a - * variable sized table containing 8-byte entries - * for offsets larger than 2^31. - */ - unsigned long min_size = 8 + 4*256 + nr*(20 + 4 + 4) + 20 + 20; - unsigned long max_size = min_size; - - if (nr) - max_size += (nr - 1)*8; - - if (idx_size < min_size || idx_size > max_size) { - gitfo_free_map(&p->index_map); - return git__throw(GIT_EOBJCORRUPTED, "Failed to check index. Wrong index size"); - } - - /* Make sure that off_t is big enough to access the whole pack... - * Is this an issue in libgit2? It shouldn't. */ - if (idx_size != min_size && (sizeof(off_t) <= 4)) { - gitfo_free_map(&p->index_map); - return git__throw(GIT_EOSERR, "Failed to check index. off_t not big enough to access the whole pack"); - } - } - - p->index_version = version; - p->num_objects = nr; - return GIT_SUCCESS; -} - -static int pack_index_open(struct pack_file *p) -{ - char *idx_name; - int error; - - if (p->index_map.data) - return GIT_SUCCESS; - - idx_name = git__strdup(p->pack_name); - strcpy(idx_name + strlen(idx_name) - STRLEN(".pack"), ".idx"); - - error = pack_index_check(idx_name, p); - free(idx_name); - - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to open index"); -} - - - - - - - - - -/*********************************************************** - * - * PACKFILE METHODS - * - ***********************************************************/ - static int packfile_sort__cb(const void *a_, const void *b_) { - struct pack_file *a = *((struct pack_file **)a_); - struct pack_file *b = *((struct pack_file **)b_); + const struct git_pack_file *a = a_; + const struct git_pack_file *b = b_; int st; /* * Local packs tend to contain objects specific to our - * variant of the project than remote ones. In addition, + * variant of the project than remote ones. In addition, * remote ones could be on a network mounted filesystem. * Favor local ones for these reasons. */ @@ -721,174 +205,32 @@ static int packfile_sort__cb(const void *a_, const void *b_) return -1; } -static struct pack_file *packfile_alloc(int extra) -{ - struct pack_file *p = git__malloc(sizeof(*p) + extra); - memset(p, 0, sizeof(*p)); - p->pack_fd = -1; - return p; -} - - -static void packfile_free(struct pack_backend *backend, struct pack_file *p) -{ - assert(p); - - /* clear_delta_base_cache(); */ - pack_window_free_all(backend, p); - - if (p->pack_fd != -1) - gitfo_close(p->pack_fd); - pack_index_free(p); - free(p->bad_object_sha1); - free(p); -} - -static int packfile_open(struct pack_file *p) -{ - struct stat st; - struct pack_header hdr; - 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"); - - /* TODO: open with noatime */ - p->pack_fd = gitfo_open(p->pack_name, O_RDONLY); - if (p->pack_fd < 0 || gitfo_fstat(p->pack_fd, &st) < GIT_SUCCESS) - return git__throw(GIT_EOSERR, "Failed to open packfile. File appears to be corrupted"); - - /* If we created the struct before we had the pack we lack size. */ - if (!p->pack_size) { - if (!S_ISREG(st.st_mode)) - goto cleanup; - p->pack_size = (off_t)st.st_size; - } else if (p->pack_size != st.st_size) - goto cleanup; - -#if 0 - /* We leave these file descriptors open with sliding mmap; - * there is no point keeping them open across exec(), though. - */ - fd_flag = fcntl(p->pack_fd, F_GETFD, 0); - if (fd_flag < 0) - return error("cannot determine file descriptor flags"); - - fd_flag |= FD_CLOEXEC; - if (fcntl(p->pack_fd, F_SETFD, fd_flag) == -1) - return GIT_EOSERR; -#endif - - /* Verify we recognize this pack file format. */ - if (gitfo_read(p->pack_fd, &hdr, sizeof(hdr)) < GIT_SUCCESS) - goto cleanup; - - if (hdr.hdr_signature != htonl(PACK_SIGNATURE)) - goto cleanup; - - if (!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 (gitfo_lseek(p->pack_fd, p->pack_size - GIT_OID_RAWSZ, SEEK_SET) == -1) - goto cleanup; - - if (gitfo_read(p->pack_fd, sha1.id, GIT_OID_RAWSZ) < GIT_SUCCESS) - 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; - -cleanup: - gitfo_close(p->pack_fd); - p->pack_fd = -1; - return git__throw(GIT_EPACKCORRUPTED, "Failed to packfile. Pack is corrupted"); -} - -static int packfile_check(struct pack_file **pack_out, const char *path) -{ - struct stat st; - struct pack_file *p; - size_t path_len; - - *pack_out = NULL; - path_len = strlen(path); - p = packfile_alloc(path_len + 2); - - /* - * Make sure a corresponding .pack file exists and that - * the index looks sane. - */ - path_len -= STRLEN(".idx"); - if (path_len < 1) { - free(p); - return git__throw(GIT_ENOTFOUND, "Failed to check packfile. Wrong path name"); - } - - memcpy(p->pack_name, path, path_len); - - strcpy(p->pack_name + path_len, ".keep"); - if (gitfo_exists(p->pack_name) == GIT_SUCCESS) - p->pack_keep = 1; - - strcpy(p->pack_name + path_len, ".pack"); - if (gitfo_stat(p->pack_name, &st) < GIT_SUCCESS || !S_ISREG(st.st_mode)) { - free(p); - return git__throw(GIT_ENOTFOUND, "Failed to check packfile. File not found"); - } - - /* ok, it looks sane as far as we can check without - * actually mapping the pack file. - */ - p->pack_size = (off_t)st.st_size; - p->pack_local = 1; - p->mtime = (git_time_t)st.st_mtime; - - /* see if we can parse the sha1 oid in the packfile name */ - if (path_len < 40 || - git_oid_mkstr(&p->sha1, path + path_len - GIT_OID_HEXSZ) < GIT_SUCCESS) - memset(&p->sha1, 0x0, GIT_OID_RAWSZ); - - *pack_out = p; - return GIT_SUCCESS; -} - -static int packfile_load__cb(void *_data, char *path) +static int packfile_load__cb(void *_data, git_buf *path) { struct pack_backend *backend = (struct pack_backend *)_data; - struct pack_file *pack; + struct git_pack_file *pack; int error; - size_t i; + unsigned int i; - if (git__suffixcmp(path, ".idx") != 0) - return GIT_SUCCESS; /* not an index */ + if (git__suffixcmp(path->ptr, ".idx") != 0) + return 0; /* not an index */ for (i = 0; i < backend->packs.length; ++i) { - struct pack_file *p = git_vector_get(&backend->packs, i); - if (memcmp(p->pack_name, path, strlen(path) - STRLEN(".idx")) == 0) - return GIT_SUCCESS; + struct git_pack_file *p = git_vector_get(&backend->packs, i); + if (memcmp(p->pack_name, git_buf_cstr(path), git_buf_len(path) - strlen(".idx")) == 0) + return 0; } - error = packfile_check(&pack, path); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to load packfile"); - - if (git_vector_insert(&backend->packs, pack) < GIT_SUCCESS) { - free(pack); - return GIT_ENOMEM; - } + error = git_packfile_check(&pack, path->ptr); + if (error == GIT_ENOTFOUND) + /* ignore missing .pack file as git does */ + 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) @@ -897,534 +239,104 @@ static int packfile_refresh_all(struct pack_backend *backend) struct stat st; if (backend->pack_folder == NULL) - return GIT_SUCCESS; + return 0; - if (gitfo_stat(backend->pack_folder, &st) < 0 || !S_ISDIR(st.st_mode)) - return git__throw(GIT_ENOTFOUND, "Failed to refresh packfiles. Backend not found"); + if (p_stat(backend->pack_folder, &st) < 0 || !S_ISDIR(st.st_mode)) + return git_odb__error_notfound("failed to refresh packfiles", NULL); if (st.st_mtime != backend->pack_folder_mtime) { - char path[GIT_PATH_MAX]; - strcpy(path, backend->pack_folder); + git_buf path = GIT_BUF_INIT; + git_buf_sets(&path, backend->pack_folder); /* reload all packs */ - error = gitfo_dirent(path, GIT_PATH_MAX, packfile_load__cb, (void *)backend); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to refresh packfiles"); - - git_vector_sort(&backend->packs); - backend->pack_folder_mtime = st.st_mtime; - } - - return GIT_SUCCESS; -} - - - - - - - - -/*********************************************************** - * - * PACKFILE ENTRY SEARCH INTERNALS - * - ***********************************************************/ - -static off_t nth_packed_object_offset(const struct pack_file *p, uint32_t n) -{ - const unsigned char *index = p->index_map.data; - index += 4 * 256; - if (p->index_version == 1) { - return ntohl(*((uint32_t *)(index + 24 * n))); - } else { - uint32_t off; - index += 8 + p->num_objects * (20 + 4); - off = ntohl(*((uint32_t *)(index + 4 * n))); - if (!(off & 0x80000000)) - return off; - index += p->num_objects * 4 + (off & 0x7fffffff) * 8; - return (((uint64_t)ntohl(*((uint32_t *)(index + 0)))) << 32) | - ntohl(*((uint32_t *)(index + 4))); - } -} - -static int pack_entry_find_offset( - off_t *offset_out, - git_oid *found_oid, - struct 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; - unsigned hi, lo, stride; - int pos, found = 0; - const unsigned char *current; - - *offset_out = 0; - - if (index == NULL) { - int error; - - if ((error = pack_index_open(p)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to find offset for pack entry"); - - assert(p->index_map.data); - - index = p->index_map.data; - level1_ofs = p->index_map.data; - } - - if (p->index_version > 1) { - level1_ofs += 2; - index += 8; - } - - index += 4 * 256; - hi = ntohl(level1_ofs[(int)short_oid->id[0]]); - lo = ((short_oid->id[0] == 0x0) ? 0 : ntohl(level1_ofs[(int)short_oid->id[0] - 1])); - - if (p->index_version > 1) { - stride = 20; - } else { - stride = 24; - index += 4; - } - -#ifdef INDEX_DEBUG_LOOKUP - printf("%02x%02x%02x... lo %u hi %u nr %d\n", - short_oid->id[0], short_oid->id[1], short_oid->id[2], lo, hi, p->num_objects); -#endif - - /* Use git.git lookup code */ - pos = sha1_entry_pos(index, stride, 0, lo, hi, p->num_objects, short_oid->id); - - if (pos >= 0) { - /* An object matching exactly the oid was found */ - found = 1; - current = index + pos * stride; - } else { - /* No object was found */ - /* pos refers to the object with the "closest" oid to short_oid */ - pos = - 1 - pos; - if (pos < (int)p->num_objects) { - current = index + pos * stride; - - if (!git_oid_match_raw(len, short_oid->id, current)) { - found = 1; - } - } - } - - if (found && pos + 1 < (int)p->num_objects) { - /* Check for ambiguousity */ - const unsigned char *next = current + stride; - - if (!git_oid_match_raw(len, short_oid->id, next)) { - found = 2; - } - } - - 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_mkraw(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; - } -} + error = git_path_direach(&path, packfile_load__cb, (void *)backend); -static int pack_entry_find1( - struct pack_entry *e, - struct pack_file *p, - const git_oid *short_oid, - unsigned int len) -{ - off_t offset; - git_oid found_oid; - int error; + git_buf_free(&path); - assert(p); + if (error < 0) + return error; - if (len == GIT_OID_HEXSZ && p->num_bad_objects) { - 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"); + git_vector_sort(&backend->packs); + backend->pack_folder_mtime = st.st_mtime; } - 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"); - - /* we found a unique entry in the index; - * make sure the packfile backing the index - * still exists on disk */ - if (p->pack_fd == -1 && packfile_open(p) < GIT_SUCCESS) - return git__throw(GIT_EOSERR, "Failed to find pack entry. Packfile doesn't exist on disk"); - - e->offset = offset; - e->p = p; - - git_oid_cpy(&e->sha1, &found_oid); - return GIT_SUCCESS; + return 0; } -static int pack_entry_find(struct pack_entry *e, struct pack_backend *backend, const git_oid *oid) +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 && - pack_entry_find1(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 pack_file *p; + struct git_pack_file *p; p = git_vector_get(&backend->packs, i); if (p == backend->last_found) continue; - if (pack_entry_find1(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", oid); } static int pack_entry_find_prefix( - struct pack_entry *e, + struct git_pack_entry *e, struct pack_backend *backend, const git_oid *short_oid, 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 = pack_entry_find1(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) { + error = git_pack_entry_find(e, backend->last_found, short_oid, len); + if (error == GIT_EAMBIGUOUS) + return error; + if (!error) found = 1; - } } for (i = 0; i < backend->packs.length; ++i) { - struct pack_file *p; + struct git_pack_file *p; p = git_vector_get(&backend->packs, i); if (p == backend->last_found) continue; - error = pack_entry_find1(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) + error = git_pack_entry_find(e, p, short_oid, len); + 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; - } - -} - - - - - - - - - - - - -/*********************************************************** - * - * PACKFILE ENTRY UNPACK INTERNALS - * - ***********************************************************/ - -static unsigned long packfile_unpack_header1( - size_t *sizep, - git_otype *type, - const unsigned char *buf, - unsigned long len) -{ - unsigned shift; - unsigned long size, c; - unsigned long used = 0; - - c = buf[used++]; - *type = (c >> 4) & 7; - size = c & 15; - shift = 4; - while (c & 0x80) { - if (len <= used || bitsizeof(long) <= shift) - return 0; - - c = buf[used++]; - size += (c & 0x7f) << shift; - shift += 7; - } - - *sizep = (size_t)size; - return used; -} - -static int packfile_unpack_header( - size_t *size_p, - git_otype *type_p, - struct pack_backend *backend, - struct pack_file *p, - struct pack_window **w_curs, - off_t *curpos) -{ - unsigned char *base; - unsigned int left; - unsigned long used; - - /* pack_window_open() assures us we have [base, base + 20) available - * as a range that we can look at at. (Its actually the hash - * size that is assured.) With our object header encoding - * the maximum deflated object size is 2^137, which is just - * insane, so we know won't exceed what we have been given. - */ - base = pack_window_open(backend, p, w_curs, *curpos, &left); - if (base == NULL) - return GIT_ENOMEM; - - used = packfile_unpack_header1(size_p, type_p, base, left); - - if (used == 0) - return git__throw(GIT_EOBJCORRUPTED, "Header length is zero"); - - *curpos += used; - return GIT_SUCCESS; -} - -static int packfile_unpack_compressed( - git_rawobj *obj, - struct pack_backend *backend, - struct pack_file *p, - struct pack_window **w_curs, - off_t curpos, - size_t size, - git_otype type) -{ - int st; - z_stream stream; - unsigned char *buffer, *in; - - buffer = git__malloc(size); - - memset(&stream, 0, sizeof(stream)); - stream.next_out = buffer; - stream.avail_out = size + 1; - - st = inflateInit(&stream); - if (st != Z_OK) { - free(buffer); - return git__throw(GIT_EZLIB, "Error in zlib"); - } - - do { - in = pack_window_open(backend, p, w_curs, curpos, &stream.avail_in); - stream.next_in = in; - st = inflate(&stream, Z_FINISH); - - if (!stream.avail_out) - break; /* the payload is larger than it should be */ - - curpos += stream.next_in - in; - } while (st == Z_OK || st == Z_BUF_ERROR); - - inflateEnd(&stream); - - if ((st != Z_STREAM_END) || stream.total_out != size) { - free(buffer); - return git__throw(GIT_EZLIB, "Error in zlib"); - } - - obj->type = type; - obj->len = size; - obj->data = buffer; - return GIT_SUCCESS; -} - -static off_t get_delta_base( - struct pack_backend *backend, - struct pack_file *p, - struct pack_window **w_curs, - off_t *curpos, - git_otype type, - off_t delta_obj_offset) -{ - unsigned char *base_info = pack_window_open(backend, p, w_curs, *curpos, NULL); - off_t base_offset; - git_oid unused; - - /* 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 - * that is assured. An OFS_DELTA longer than the hash size - * is stupid, as then a REF_DELTA would be smaller to store. - */ - if (type == GIT_OBJ_OFS_DELTA) { - unsigned used = 0; - unsigned char c = base_info[used++]; - base_offset = c & 127; - while (c & 128) { - base_offset += 1; - if (!base_offset || MSB(base_offset, 7)) - return 0; /* overflow */ - c = base_info[used++]; - base_offset = (base_offset << 7) + (c & 127); - } - base_offset = delta_obj_offset - base_offset; - if (base_offset <= 0 || base_offset >= delta_obj_offset) - return 0; /* out of bound */ - *curpos += used; - } else if (type == GIT_OBJ_REF_DELTA) { - /* 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__throw(GIT_EPACKCORRUPTED, "Base entry delta is not in the same pack"); - *curpos += 20; - } else + if (!found) + return git_odb__error_notfound("no matching pack entry for prefix", short_oid); + else if (found > 1) + return git_odb__error_ambiguous("found multiple pack entries"); + else return 0; - - return base_offset; } -static int packfile_unpack_delta( - git_rawobj *obj, - struct pack_backend *backend, - struct pack_file *p, - struct pack_window **w_curs, - off_t curpos, - size_t delta_size, - git_otype delta_type, - off_t obj_offset) -{ - off_t base_offset; - git_rawobj base, delta; - int error; - - base_offset = get_delta_base(backend, p, w_curs, &curpos, delta_type, obj_offset); - if (base_offset == 0) - return git__throw(GIT_EOBJCORRUPTED, "Delta offset is zero"); - - pack_window_close(w_curs); - error = packfile_unpack(&base, backend, p, base_offset); - - /* TODO: git.git tries to load the base from other packfiles - * or loose objects */ - if (error < GIT_SUCCESS) - return git__rethrow(error, "Corrupted delta"); - - error = packfile_unpack_compressed(&delta, backend, p, w_curs, curpos, delta_size, delta_type); - if (error < GIT_SUCCESS) { - free(base.data); - return git__rethrow(error, "Corrupted delta"); - } - - obj->type = base.type; - error = git__delta_apply(obj, - base.data, base.len, - delta.data, delta.len); - - free(base.data); - 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 */ -} - -static int packfile_unpack( - git_rawobj *obj, - struct pack_backend *backend, - struct pack_file *p, - off_t obj_offset) -{ - struct pack_window *w_curs = NULL; - off_t curpos = obj_offset; - int error; - - size_t size = 0; - git_otype type; - - /* - * TODO: optionally check the CRC on the packfile - */ - - obj->data = NULL; - obj->len = 0; - obj->type = GIT_OBJ_BAD; - - error = packfile_unpack_header(&size, &type, backend, p, &w_curs, &curpos); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to unpack packfile"); - - switch (type) { - case GIT_OBJ_OFS_DELTA: - case GIT_OBJ_REF_DELTA: - error = packfile_unpack_delta( - obj, backend, p, &w_curs, curpos, - size, type, obj_offset); - break; - - case GIT_OBJ_COMMIT: - case GIT_OBJ_TREE: - case GIT_OBJ_BLOB: - case GIT_OBJ_TAG: - error = packfile_unpack_compressed( - obj, backend, p, &w_curs, curpos, - size, type); - break; - - default: - error = GIT_EOBJCORRUPTED; - break; - } - - pack_window_close(&w_curs); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to unpack packfile"); -} - - - - /*********************************************************** * @@ -1448,26 +360,24 @@ int pack_backend__read_header(git_rawobj *obj, git_odb_backend *backend, const g } */ -int pack_backend__read(void **buffer_p, size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid) +static int pack_backend__read(void **buffer_p, size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid) { - struct pack_entry e; + struct git_pack_entry e; 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 = packfile_unpack(&raw, (struct pack_backend *)backend, 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; } -int pack_backend__read_prefix( +static int pack_backend__read_prefix( git_oid *out_oid, void **buffer_p, size_t *len_p, @@ -1476,87 +386,77 @@ 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 pack_entry e; + 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 = packfile_unpack(&raw, (struct pack_backend *)backend, 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; } -int pack_backend__exists(git_odb_backend *backend, const git_oid *oid) +static int pack_backend__exists(git_odb_backend *backend, const git_oid *oid) { - struct pack_entry e; - return pack_entry_find(&e, (struct pack_backend *)backend, oid) == GIT_SUCCESS; + struct git_pack_entry e; + return pack_entry_find(&e, (struct pack_backend *)backend, oid) == 0; } -void pack_backend__free(git_odb_backend *_backend) +static void pack_backend__free(git_odb_backend *_backend) { struct pack_backend *backend; - size_t i; + unsigned int i; assert(_backend); backend = (struct pack_backend *)_backend; for (i = 0; i < backend->packs.length; ++i) { - struct pack_file *p = git_vector_get(&backend->packs, i); - packfile_free(backend, p); + struct git_pack_file *p = git_vector_get(&backend->packs, i); + packfile_free(p); } git_vector_free(&backend->packs); - free(backend->pack_folder); - free(backend); + git__free(backend->pack_folder); + git__free(backend); } int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir) { - struct pack_backend *backend; - char path[GIT_PATH_MAX]; + struct pack_backend *backend = NULL; + git_buf path = GIT_BUF_INIT; backend = git__calloc(1, sizeof(struct pack_backend)); - if (backend == NULL) - return GIT_ENOMEM; + GITERR_CHECK_ALLOC(backend); - if (git_vector_init(&backend->packs, 8, packfile_sort__cb) < GIT_SUCCESS) { - free(backend); - return GIT_ENOMEM; + if (git_vector_init(&backend->packs, 8, packfile_sort__cb) < 0 || + git_buf_joinpath(&path, objects_dir, "pack") < 0) + { + git__free(backend); + return -1; } - backend->window_size = DEFAULT_WINDOW_SIZE; - backend->mapped_limit = DEFAULT_MAPPED_LIMIT; - - git__joinpath(path, objects_dir, "pack"); - if (gitfo_isdir(path) == GIT_SUCCESS) { - backend->pack_folder = git__strdup(path); + if (git_path_isdir(git_buf_cstr(&path)) == true) { + backend->pack_folder = git_buf_detach(&path); backend->pack_folder_mtime = 0; - - if (backend->pack_folder == NULL) { - free(backend); - return GIT_ENOMEM; - } } backend->parent.read = &pack_backend__read; @@ -1566,5 +466,8 @@ int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir) backend->parent.free = &pack_backend__free; *backend_out = (git_odb_backend *)backend; - return GIT_SUCCESS; + + git_buf_free(&path); + + return 0; } @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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" @@ -29,37 +11,52 @@ #include <string.h> #include <limits.h> -static signed char from_hex[] = { --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 00 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 10 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 20 */ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, /* 30 */ --1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 40 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 50 */ --1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 60 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 70 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 80 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 90 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* a0 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* b0 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* c0 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* d0 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* e0 */ --1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* f0 */ -}; static char to_hex[] = "0123456789abcdef"; -int git_oid_mkstr(git_oid *out, const char *str) +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; - for (p = 0; p < sizeof(out->id); p++, str += 2) { - int v = (from_hex[(unsigned char)str[0]] << 4) - | from_hex[(unsigned char)str[1]]; + int v; + + if (length < 4) + return oid_error_invalid("input too short"); + + if (length > GIT_OID_HEXSZ) + length = GIT_OID_HEXSZ; + + for (p = 0; p < length - 1; p += 2) { + v = (git__fromhex(str[p + 0]) << 4) + | 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"); - out->id[p] = (unsigned char)v; + return oid_error_invalid("contains invalid characters"); + + out->id[p / 2] = (unsigned char)v; + } + + if (length % 2) { + v = (git__fromhex(str[p + 0]) << 4); + if (v < 0) + return oid_error_invalid("contains invalid characters"); + + out->id[p / 2] = (unsigned char)v; + p += 2; } - return GIT_SUCCESS; + + memset(out->id + p / 2, 0, (GIT_OID_HEXSZ - p) / 2); + + return 0; +} + +int git_oid_fromstr(git_oid *out, const char *str) +{ + return git_oid_fromstrn(out, str, GIT_OID_HEXSZ); } GIT_INLINE(char) *fmt_one(char *str, unsigned int val) @@ -97,14 +94,14 @@ char *git_oid_allocfmt(const git_oid *oid) return str; } -char *git_oid_to_string(char *out, size_t n, const git_oid *oid) +char *git_oid_tostr(char *out, size_t n, const git_oid *oid) { char str[GIT_OID_HEXSZ]; if (!out || n == 0 || !oid) return ""; - n--; /* allow room for terminating NUL */ + n--; /* allow room for terminating NUL */ if (n > 0) { git_oid_fmt(str, oid); @@ -118,8 +115,9 @@ char *git_oid_to_string(char *out, size_t n, const git_oid *oid) return out; } -int git__parse_oid(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); @@ -127,37 +125,33 @@ int git__parse_oid(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_mkstr(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; } -int git__write_oid(git_odb_stream *stream, const char *header, const git_oid *oid) +void git_oid__writebuf(git_buf *buf, const char *header, const git_oid *oid) { - char hex_oid[42]; - - git_oid_fmt(hex_oid + 1, oid); - - hex_oid[0] = ' '; - hex_oid[41] = '\n'; + char hex_oid[GIT_OID_HEXSZ]; - stream->write(stream, header, strlen(header)); - stream->write(stream, hex_oid, 42); - return GIT_SUCCESS; + git_oid_fmt(hex_oid, oid); + git_buf_puts(buf, header); + git_buf_put(buf, hex_oid, GIT_OID_HEXSZ); + git_buf_putc(buf, '\n'); } -void git_oid_mkraw(git_oid *out, const unsigned char *raw) +void git_oid_fromraw(git_oid *out, const unsigned char *raw) { memcpy(out->id, raw, sizeof(out->id)); } @@ -172,30 +166,44 @@ int git_oid_cmp(const git_oid *a, const git_oid *b) return memcmp(a->id, b->id, sizeof(a->id)); } - -int git_oid_match_raw(unsigned int len, const unsigned char *a, const unsigned char *b) +int git_oid_ncmp(const git_oid *oid_a, const git_oid *oid_b, unsigned int len) { - do { - if (*a != *b) - return 1; - a++; - b++; - len -= 2; - } while (len > 1); - if (len) - if ((*a ^ *b) & 0xf0) - return 1; - return 0; + const unsigned char *a = oid_a->id; + const unsigned char *b = oid_b->id; + + do { + if (*a != *b) + return 1; + a++; + b++; + len -= 2; + } while (len > 1); + + if (len) + if ((*a ^ *b) & 0xf0) + return 1; + + return 0; } -int git_oid_match_hex(unsigned int len, const unsigned char *a, const unsigned char *b) +int git_oid_streq(const git_oid *a, const char *str) { - return memcmp(a, b, len); + git_oid id; + + if (git_oid_fromstr(&id, str) < 0) + return -1; + + return git_oid_cmp(a, &id) == 0 ? 0 : -1; } -int gid_oid_match(unsigned int len, git_oid *a, git_oid *b) +int git_oid_iszero(const git_oid *oid_a) { - return git_oid_match_raw(len, a->id, b->id); + const unsigned char *a = oid_a->id; + unsigned int i; + for (i = 0; i < GIT_OID_RAWSZ; ++i, ++a) + if (*a != 0) + return 0; + return 1; } typedef short node_index; @@ -213,16 +221,15 @@ struct git_oid_shorten { static int resize_trie(git_oid_shorten *self, size_t new_size) { - self->nodes = realloc(self->nodes, new_size * sizeof(trie_node)); - if (self->nodes == NULL) - return GIT_ENOMEM; + self->nodes = git__realloc(self->nodes, new_size * sizeof(trie_node)); + 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) @@ -231,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; } @@ -253,27 +260,27 @@ 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) { - free(os); + 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; } void git_oid_shorten_free(git_oid_shorten *os) { - free(os->nodes); - free(os); + git__free(os->nodes); + git__free(os); } @@ -293,7 +300,7 @@ void git_oid_shorten_free(git_oid_shorten *os) * * - Each normal node points to 16 children (one for each possible * character in the oid). This is *not* stored in an array of - * pointers, because in a 64-bit arch this would be sucking + * pointers, because in a 64-bit arch this would be sucking * 16*sizeof(void*) = 128 bytes of memory per node, which is fucking * insane. What we do is store Node Indexes, and use these indexes * to look up each node in the om->index array. These indexes are @@ -323,21 +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 = from_hex[(int)text_oid[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]; @@ -347,23 +360,22 @@ int git_oid_shorten_add(git_oid_shorten *os, const char *text_oid) tail = node->tail; node->tail = NULL; - node = push_leaf(os, idx, from_hex[(int)tail[0]], &tail[1]); - if (node == NULL) - return GIT_ENOMEM; + node = push_leaf(os, idx, git__fromhex(tail[0]), &tail[1]); + 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/oid.h b/src/oid.h deleted file mode 100644 index 1cd2450d6..000000000 --- a/src/oid.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef INCLUDE_oid_h__ -#define INCLUDE_oid_h__ - -/** - * Compare the first ('len'*4) bits of two raw formatted oids. - * This can be useful for internal use. - * Return 0 if they match. - */ -int git_oid_match_raw(unsigned int len, const unsigned char *a, const unsigned char *b); - -/** - * Compare the first 'len' characters of two hex formatted oids. - * Return 0 if they match. - */ -int git_oid_match_hex(unsigned int len, const unsigned char *a, const unsigned char *b); - -#endif 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 new file mode 100644 index 000000000..0db1069de --- /dev/null +++ b/src/pack.c @@ -0,0 +1,816 @@ +/* + * 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 "odb.h" +#include "pack.h" +#include "delta-apply.h" +#include "sha1_lookup.h" +#include "mwindow.h" +#include "fileops.h" + +#include "git2/oid.h" +#include <zlib.h> + +static int packfile_open(struct git_pack_file *p); +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, + git_off_t *curpos, + size_t size, + git_otype type); + +/* Can find the offset of an object given + * a prefix of an identifier. + * Throws GIT_EAMBIGUOUSOIDPREFIX if short oid + * is ambiguous within the pack. + * This method assumes that len is between + * GIT_OID_MINPREFIXLEN and GIT_OID_HEXSZ. + */ +static int pack_entry_find_offset( + 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 + * + ***********************************************************/ + +static void pack_index_free(struct git_pack_file *p) +{ + if (p->index_map.data) { + git_futils_mmap_free(&p->index_map); + p->index_map.data = NULL; + } +} + +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; + 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 fd; + + 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); + 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 < 0) + return error; + + hdr = idx_map = p->index_map.data; + + if (hdr->idx_signature == htonl(PACK_IDX_SIGNATURE)) { + version = ntohl(hdr->idx_version); + + if (version < 2 || version > 2) { + git_futils_mmap_free(&p->index_map); + return packfile_error("unsupported index version"); + } + + } else + version = 1; + + nr = 0; + index = idx_map; + + if (version > 1) + index += 2; /* skip index header */ + + for (i = 0; i < 256; i++) { + uint32_t n = ntohl(index[i]); + if (n < nr) { + git_futils_mmap_free(&p->index_map); + return packfile_error("index is non-monotonic"); + } + nr = n; + } + + if (version == 1) { + /* + * Total size: + * - 256 index entries 4 bytes each + * - 24-byte entries * nr (20-byte sha1 + 4-byte offset) + * - 20-byte SHA1 of the packfile + * - 20-byte SHA1 file checksum + */ + if (idx_size != 4*256 + nr * 24 + 20 + 20) { + git_futils_mmap_free(&p->index_map); + return packfile_error("index is corrupted"); + } + } else if (version == 2) { + /* + * Minimum size: + * - 8 bytes of header + * - 256 index entries 4 bytes each + * - 20-byte sha1 entry * nr + * - 4-byte crc entry * nr + * - 4-byte offset entry * nr + * - 20-byte SHA1 of the packfile + * - 20-byte SHA1 file checksum + * And after the 4-byte offset table might be a + * variable sized table containing 8-byte entries + * for offsets larger than 2^31. + */ + unsigned long min_size = 8 + 4*256 + nr*(20 + 4 + 4) + 20 + 20; + unsigned long max_size = min_size; + + if (nr) + max_size += (nr - 1)*8; + + if (idx_size < min_size || idx_size > max_size) { + git_futils_mmap_free(&p->index_map); + return packfile_error("wrong index size"); + } + } + + p->index_version = version; + p->num_objects = nr; + 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 0; + + idx_name = git__strdup(p->pack_name); + 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; +} + +static unsigned char *pack_window_open( + struct git_pack_file *p, + git_mwindow **w_cursor, + git_off_t offset, + unsigned int *left) +{ + if (p->mwf.fd == -1 && packfile_open(p) < 0) + return NULL; + + /* Since packfiles end in a hash of their content and it's + * pointless to ask for an offset into the middle of that + * hash, and the pack_window_contains function above wouldn't match + * don't allow an offset too close to the end of the file. + */ + if (offset > (p->mwf.size - 20)) + return NULL; + + return git_mwindow_open(&p->mwf, w_cursor, offset, 20, left); + } + +static int packfile_unpack_header1( + unsigned long *usedp, + size_t *sizep, + git_otype *type, + const unsigned char *buf, + unsigned long len) +{ + unsigned shift; + unsigned long size, c; + unsigned long used = 0; + + c = buf[used++]; + *type = (c >> 4) & 7; + size = c & 15; + shift = 4; + while (c & 0x80) { + if (len <= used) + return GIT_EBUFS; + + if (bitsizeof(long) <= shift) { + *usedp = 0; + return -1; + } + + c = buf[used++]; + size += (c & 0x7f) << shift; + shift += 7; + } + + *sizep = (size_t)size; + *usedp = used; + return 0; +} + +int git_packfile_unpack_header( + size_t *size_p, + git_otype *type_p, + git_mwindow_file *mwf, + git_mwindow **w_curs, + 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 + * size that is assured.) With our object header encoding + * the maximum deflated object size is 2^137, which is just + * insane, so we know won't exceed what we have been given. + */ +// base = pack_window_open(p, w_curs, *curpos, &left); + base = git_mwindow_open(mwf, w_curs, *curpos, 20, &left); + if (base == NULL) + return GIT_EBUFS; + + ret = packfile_unpack_header1(&used, size_p, type_p, base, left); + git_mwindow_close(w_curs); + if (ret == GIT_EBUFS) + return ret; + else if (ret < 0) + return packfile_error("header length is zero"); + + *curpos += used; + return 0; +} + +static int packfile_unpack_delta( + git_rawobj *obj, + struct git_pack_file *p, + git_mwindow **w_curs, + git_off_t *curpos, + size_t delta_size, + git_otype delta_type, + git_off_t obj_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 packfile_error("delta offset is zero"); + if (base_offset < 0) /* must actually be an error code */ + return (int)base_offset; + + error = git_packfile_unpack(&base, p, &base_offset); + + /* + * TODO: git.git tries to load the base from other packfiles + * or loose objects. + * + * We'll need to do this in order to support thin packs. + */ + if (error < 0) + return error; + + error = packfile_unpack_compressed(&delta, p, w_curs, curpos, delta_size, delta_type); + git_mwindow_close(w_curs); + if (error < 0) { + git__free(base.data); + return error; + } + + obj->type = base.type; + 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, + git_off_t *obj_offset) +{ + git_mwindow *w_curs = NULL; + git_off_t curpos = *obj_offset; + int error; + + size_t size = 0; + git_otype type; + + /* + * TODO: optionally check the CRC on the packfile + */ + + obj->data = NULL; + obj->len = 0; + obj->type = GIT_OBJ_BAD; + + error = git_packfile_unpack_header(&size, &type, &p->mwf, &w_curs, &curpos); + git_mwindow_close(&w_curs); + + if (error < 0) + return error; + + switch (type) { + case GIT_OBJ_OFS_DELTA: + case GIT_OBJ_REF_DELTA: + error = packfile_unpack_delta( + obj, p, &w_curs, &curpos, + size, type, *obj_offset); + break; + + case GIT_OBJ_COMMIT: + case GIT_OBJ_TREE: + case GIT_OBJ_BLOB: + case GIT_OBJ_TAG: + error = packfile_unpack_compressed( + obj, p, &w_curs, &curpos, + size, type); + break; + + default: + error = packfile_error("invalid packfile type in header");; + break; + } + + *obj_offset = curpos; + return error; +} + +static void *use_git_alloc(void *opaq, unsigned int count, unsigned int size) +{ + GIT_UNUSED(opaq); + return git__calloc(count, size); +} + +static void use_git_free(void *opaq, void *ptr) +{ + GIT_UNUSED(opaq); + git__free(ptr); +} + +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) +{ + int st; + z_stream stream; + unsigned char *buffer, *in; + + buffer = git__calloc(1, size + 1); + GITERR_CHECK_ALLOC(buffer); + + memset(&stream, 0, sizeof(stream)); + stream.next_out = buffer; + stream.avail_out = (uInt)size + 1; + stream.zalloc = use_git_alloc; + stream.zfree = use_git_free; + + st = inflateInit(&stream); + if (st != Z_OK) { + git__free(buffer); + 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_EBUFS; + } + + *curpos += stream.next_in - in; + } while (st == Z_OK || st == Z_BUF_ERROR); + + inflateEnd(&stream); + + if ((st != Z_STREAM_END) || stream.total_out != size) { + git__free(buffer); + giterr_set(GITERR_ZLIB, "Failed to inflate packfile"); + return -1; + } + + obj->type = type; + obj->len = size; + obj->data = buffer; + return 0; +} + +/* + * curpos is where the data starts, delta_obj_offset is the where the + * header starts + */ +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 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_EBUFS; + /* 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 + * that is assured. An OFS_DELTA longer than the hash size + * is stupid, as then a REF_DELTA would be smaller to store. + */ + if (type == GIT_OBJ_OFS_DELTA) { + unsigned used = 0; + unsigned char c = base_info[used++]; + base_offset = c & 127; + while (c & 128) { + if (left <= used) + return GIT_EBUFS; + base_offset += 1; + if (!base_offset || MSB(base_offset, 7)) + return 0; /* overflow */ + c = base_info[used++]; + base_offset = (base_offset << 7) + (c & 127); + } + base_offset = delta_obj_offset - base_offset; + if (base_offset <= 0 || base_offset >= delta_obj_offset) + return 0; /* out of bound */ + *curpos += used; + } else if (type == GIT_OBJ_REF_DELTA) { + /* If we have the cooperative cache, search in it first */ + if (p->has_cache) { + int pos; + struct git_pack_entry key; + + git_oid_fromraw(&key.sha1, base_info); + pos = git_vector_bsearch(&p->cache, &key); + if (pos >= 0) { + *curpos += 20; + return ((struct git_pack_entry *)git_vector_get(&p->cache, pos))->offset; + } + } + /* 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) < 0) + return packfile_error("base entry delta is not in the same pack"); + *curpos += 20; + } else + return 0; + + return base_offset; +} + +/*********************************************************** + * + * PACKFILE METHODS + * + ***********************************************************/ + +static struct git_pack_file *packfile_alloc(size_t extra) +{ + struct git_pack_file *p = git__calloc(1, sizeof(*p) + extra); + if (p != NULL) + p->mwf.fd = -1; + return p; +} + + +void packfile_free(struct git_pack_file *p) +{ + assert(p); + + /* clear_delta_base_cache(); */ + git_mwindow_free_all(&p->mwf); + + if (p->mwf.fd != -1) + p_close(p->mwf.fd); + + pack_index_free(p); + + git__free(p->bad_object_sha1); + git__free(p); +} + +static int packfile_open(struct git_pack_file *p) +{ + struct stat st; + struct git_pack_header hdr; + git_oid sha1; + unsigned char *idx_sha1; + + assert(p->index_map.data); + + if (!p->index_map.data && pack_index_open(p) < 0) + return git_odb__error_notfound("failed to open packfile", NULL); + + /* TODO: open with noatime */ + p->mwf.fd = git_futils_open_ro(p->pack_name); + if (p->mwf.fd < 0) + return p->mwf.fd; + + 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 = (git_off_t)st.st_size; + } else if (p->mwf.size != st.st_size) + goto cleanup; + +#if 0 + /* We leave these file descriptors open with sliding mmap; + * there is no point keeping them open across exec(), though. + */ + fd_flag = fcntl(p->mwf.fd, F_GETFD, 0); + if (fd_flag < 0) + goto cleanup; + + fd_flag |= FD_CLOEXEC; + if (fcntl(p->pack_fd, F_SETFD, fd_flag) == -1) + goto cleanup; +#endif + + /* Verify we recognize this pack file format. */ + 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) || + 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) + return 0; + +cleanup: + giterr_set(GITERR_OS, "Invalid packfile '%s'", p->pack_name); + p_close(p->mwf.fd); + p->mwf.fd = -1; + return -1; +} + +int git_packfile_check(struct git_pack_file **pack_out, const char *path) +{ + struct stat st; + struct git_pack_file *p; + size_t path_len; + + *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 + * the index looks sane. + */ + path_len -= strlen(".idx"); + if (path_len < 1) { + git__free(p); + return git_odb__error_notfound("invalid packfile path", NULL); + } + + memcpy(p->pack_name, path, path_len); + + strcpy(p->pack_name + path_len, ".keep"); + 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) < 0 || !S_ISREG(st.st_mode)) { + git__free(p); + return git_odb__error_notfound("packfile not found", NULL); + } + + /* ok, it looks sane as far as we can check without + * actually mapping the pack file. + */ + p->mwf.size = st.st_size; + p->pack_local = 1; + p->mtime = (git_time_t)st.st_mtime; + + /* 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) < 0) + memset(&p->sha1, 0x0, GIT_OID_RAWSZ); + + *pack_out = p; + + return 0; +} + +/*********************************************************** + * + * PACKFILE ENTRY SEARCH INTERNALS + * + ***********************************************************/ + +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; + if (p->index_version == 1) { + return ntohl(*((uint32_t *)(index + 24 * n))); + } else { + uint32_t off; + index += 8 + p->num_objects * (20 + 4); + off = ntohl(*((uint32_t *)(index + 4 * n))); + if (!(off & 0x80000000)) + return off; + index += p->num_objects * 4 + (off & 0x7fffffff) * 8; + return (((uint64_t)ntohl(*((uint32_t *)(index + 0)))) << 32) | + ntohl(*((uint32_t *)(index + 4))); + } +} + +static int pack_entry_find_offset( + 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; + unsigned hi, lo, stride; + int pos, found = 0; + const unsigned char *current = 0; + + *offset_out = 0; + + if (index == NULL) { + int error; + + if ((error = pack_index_open(p)) < 0) + return error; + + assert(p->index_map.data); + + index = p->index_map.data; + level1_ofs = p->index_map.data; + } + + if (p->index_version > 1) { + level1_ofs += 2; + index += 8; + } + + index += 4 * 256; + hi = ntohl(level1_ofs[(int)short_oid->id[0]]); + lo = ((short_oid->id[0] == 0x0) ? 0 : ntohl(level1_ofs[(int)short_oid->id[0] - 1])); + + if (p->index_version > 1) { + stride = 20; + } else { + stride = 24; + index += 4; + } + +#ifdef INDEX_DEBUG_LOOKUP + printf("%02x%02x%02x... lo %u hi %u nr %d\n", + short_oid->id[0], short_oid->id[1], short_oid->id[2], lo, hi, p->num_objects); +#endif + + /* Use git.git lookup code */ + pos = sha1_entry_pos(index, stride, 0, lo, hi, p->num_objects, short_oid->id); + + if (pos >= 0) { + /* An object matching exactly the oid was found */ + found = 1; + current = index + pos * stride; + } else { + /* No object was found */ + /* pos refers to the object with the "closest" oid to short_oid */ + pos = - 1 - pos; + if (pos < (int)p->num_objects) { + current = index + pos * stride; + + if (!git_oid_ncmp(short_oid, (const git_oid *)current, len)) + found = 1; + } + } + + if (found && len != GIT_OID_HEXSZ && pos + 1 < (int)p->num_objects) { + /* Check for ambiguousity */ + const unsigned char *next = current + stride; + + if (!git_oid_ncmp(short_oid, (const git_oid *)next, len)) { + found = 2; + } + } + + if (!found) + return git_odb__error_notfound("failed to find offset for pack entry", short_oid); + 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 0; +} + +int git_pack_entry_find( + struct git_pack_entry *e, + struct git_pack_file *p, + const git_oid *short_oid, + unsigned int len) +{ + git_off_t offset; + git_oid found_oid; + int error; + + assert(p); + + if (len == GIT_OID_HEXSZ && p->num_bad_objects) { + unsigned i; + for (i = 0; i < p->num_bad_objects; i++) + if (git_oid_cmp(short_oid, &p->bad_object_sha1[i]) == 0) + return packfile_error("bad object found in packfile"); + } + + error = pack_entry_find_offset(&offset, &found_oid, p, short_oid, len); + 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 && (error = packfile_open(p)) < 0) + return error; + + e->offset = offset; + e->p = p; + + git_oid_cpy(&e->sha1, &found_oid); + return 0; +} diff --git a/src/pack.h b/src/pack.h new file mode 100644 index 000000000..cd7a4d2e1 --- /dev/null +++ b/src/pack.h @@ -0,0 +1,106 @@ +/* + * 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_pack_h__ +#define INCLUDE_pack_h__ + +#include "git2/oid.h" + +#include "common.h" +#include "map.h" +#include "mwindow.h" +#include "odb.h" + +#define GIT_PACK_FILE_MODE 0444 + +#define PACK_SIGNATURE 0x5041434b /* "PACK" */ +#define PACK_VERSION 2 +#define pack_version_ok(v) ((v) == htonl(2) || (v) == htonl(3)) +struct git_pack_header { + uint32_t hdr_signature; + uint32_t hdr_version; + uint32_t hdr_entries; +}; + +/* + * The first four bytes of index formats later than version 1 should + * start with this signature, as all older git binaries would find this + * value illegal and abort reading the file. + * + * This is the case because the number of objects in a packfile + * cannot exceed 1,431,660,000 as every object would need at least + * 3 bytes of data and the overall packfile cannot exceed 4 GiB with + * version 1 of the index file due to the offsets limited to 32 bits. + * Clearly the signature exceeds this maximum. + * + * Very old git binaries will also compare the first 4 bytes to the + * next 4 bytes in the index and abort with a "non-monotonic index" + * error if the second 4 byte word is smaller than the first 4 + * byte word. This would be true in the proposed future index + * format as idx_signature would be greater than idx_version. + */ + +#define PACK_IDX_SIGNATURE 0xff744f63 /* "\377tOc" */ + +struct git_pack_idx_header { + uint32_t idx_signature; + uint32_t idx_version; +}; + +struct git_pack_file { + git_mwindow_file mwf; + git_map index_map; + + uint32_t num_objects; + uint32_t num_bad_objects; + git_oid *bad_object_sha1; /* array of git_oid */ + + int index_version; + git_time_t mtime; + unsigned pack_local:1, pack_keep:1, has_cache:1; + git_oid sha1; + git_vector cache; + + /* something like ".git/objects/pack/xxxxx.pack" */ + char pack_name[GIT_FLEX_ARRAY]; /* more */ +}; + +struct git_pack_entry { + git_off_t offset; + git_oid sha1; + struct git_pack_file *p; +}; + +int git_packfile_unpack_header( + size_t *size_p, + git_otype *type_p, + git_mwindow_file *mwf, + git_mwindow **w_curs, + 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); +int git_pack_entry_find( + struct git_pack_entry *e, + struct git_pack_file *p, + const git_oid *short_oid, + unsigned int len); + +#endif diff --git a/src/path.c b/src/path.c new file mode 100644 index 000000000..84edf6d89 --- /dev/null +++ b/src/path.c @@ -0,0 +1,656 @@ +/* + * 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 "path.h" +#include "posix.h" +#ifdef GIT_WIN32 +#include "win32/dir.h" +#include "win32/posix.h" +#else +#include <dirent.h> +#endif +#include <stdarg.h> +#include <stdio.h> +#include <ctype.h> + +/* + * Based on the Android implementation, BSD licensed. + * Check http://android.git.kernel.org/ + */ +int git_path_basename_r(git_buf *buffer, const char *path) +{ + const char *endp, *startp; + int len, result; + + /* Empty or NULL string gets treated as "." */ + if (path == NULL || *path == '\0') { + startp = "."; + len = 1; + goto Exit; + } + + /* Strip trailing slashes */ + endp = path + strlen(path) - 1; + while (endp > path && *endp == '/') + endp--; + + /* All slashes becomes "/" */ + if (endp == path && *endp == '/') { + startp = "/"; + len = 1; + goto Exit; + } + + /* Find the start of the base */ + startp = endp; + while (startp > path && *(startp - 1) != '/') + startp--; + + /* Cast is safe because max path < max int */ + len = (int)(endp - startp + 1); + +Exit: + result = len; + + if (buffer != NULL && git_buf_set(buffer, startp, len) < 0) + return -1; + + return result; +} + +/* + * Based on the Android implementation, BSD licensed. + * Check http://android.git.kernel.org/ + */ +int git_path_dirname_r(git_buf *buffer, const char *path) +{ + const char *endp; + int result, len; + + /* Empty or NULL string gets treated as "." */ + if (path == NULL || *path == '\0') { + path = "."; + len = 1; + goto Exit; + } + + /* Strip trailing slashes */ + endp = path + strlen(path) - 1; + while (endp > path && *endp == '/') + endp--; + + /* Find the start of the dir */ + while (endp > path && *endp != '/') + endp--; + + /* Either the dir is "/" or there are no slashes */ + if (endp == path) { + path = (*endp == '/') ? "/" : "."; + len = 1; + goto Exit; + } + + do { + endp--; + } while (endp > path && *endp == '/'); + + /* 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 + 'C:/' here */ + + if (len == 2 && isalpha(path[0]) && path[1] == ':') { + len = 3; + goto Exit; + } +#endif + +Exit: + result = len; + + if (buffer != NULL && git_buf_set(buffer, path, len) < 0) + return -1; + + return result; +} + + +char *git_path_dirname(const char *path) +{ + git_buf buf = GIT_BUF_INIT; + char *dirname; + + git_path_dirname_r(&buf, path); + dirname = git_buf_detach(&buf); + git_buf_free(&buf); /* avoid memleak if error occurs */ + + return dirname; +} + +char *git_path_basename(const char *path) +{ + git_buf buf = GIT_BUF_INIT; + char *basename; + + git_path_basename_r(&buf, path); + basename = git_buf_detach(&buf); + git_buf_free(&buf); /* avoid memleak if error occurs */ + + return basename; +} + + +const char *git_path_topdir(const char *path) +{ + size_t len; + ssize_t i; + + assert(path); + len = strlen(path); + + if (!len || path[len - 1] != '/') + return NULL; + + for (i = (ssize_t)len - 2; i >= 0; --i) + if (path[i] == '/') + break; + + return &path[i + 1]; +} + +int git_path_root(const char *path) +{ + int offset = 0; + +#ifdef GIT_WIN32 + /* 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] == '/' || path[offset] == '\\') + return offset; + + 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) +{ + char buf[GIT_PATH_MAX]; + + assert(path && path_out); + + /* construct path if needed */ + if (base != NULL && git_path_root(path) < 0) { + if (git_buf_joinpath(path_out, base, path) < 0) + return -1; + path = path_out->ptr; + } + + if (p_realpath(path, buf) == NULL) { + /* giterr_set resets the errno when dealing with a GITERR_OS kind of error */ + int error = (errno == ENOENT || errno == ENOTDIR) ? GIT_ENOTFOUND : -1; + giterr_set(GITERR_OS, "Failed to resolve path '%s'", path); + + git_buf_clear(path_out); + + 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); + return (error < 0) ? error : git_path_to_dir(path_out); +} + +int git_path_to_dir(git_buf *path) +{ + if (path->asize > 0 && + git_buf_len(path) > 0 && + path->ptr[git_buf_len(path) - 1] != '/') + git_buf_putc(path, '/'); + + return git_buf_oom(path) ? -1 : 0; +} + +void git_path_string_to_dir(char* path, size_t size) +{ + size_t end = strlen(path); + + if (end && path[end - 1] != '/' && end < size) { + path[end] = '/'; + path[end + 1] = '\0'; + } +} + +int git__percent_decode(git_buf *decoded_out, const char *input) +{ + int len, hi, lo, i; + assert(decoded_out && input); + + len = (int)strlen(input); + git_buf_clear(decoded_out); + + for(i = 0; i < len; i++) + { + char c = input[i]; + + if (c != '%') + goto append; + + if (i >= len - 2) + goto append; + + hi = git__fromhex(input[i + 1]); + lo = git__fromhex(input[i + 2]); + + if (hi < 0 || lo < 0) + goto append; + + c = (char)(hi << 4 | lo); + i += 2; + +append: + if (git_buf_putc(decoded_out, c) < 0) + return -1; + } + + 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 offset = 0, len; + + assert(local_path_out && file_url); + + if (git__prefixcmp(file_url, "file://") != 0) + return error_invalid_local_file_uri(file_url); + + offset += 7; + 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 error_invalid_local_file_uri(file_url); + + if (offset >= len || file_url[offset] == '/') + return error_invalid_local_file_uri(file_url); + +#ifndef _MSC_VER + offset--; /* A *nix absolute path starts with a forward slash */ +#endif + + git_buf_clear(local_path_out); + + return git__percent_decode(local_path_out, file_url + offset); +} + +int git_path_walk_up( + git_buf *path, + const char *ceiling, + int (*cb)(void *data, git_buf *), + void *data) +{ + int error = 0; + git_buf iter; + ssize_t stop = 0, scan; + char oldc = '\0'; + + assert(path && cb); + + if (ceiling != NULL) { + if (git__prefixcmp(path->ptr, ceiling) == 0) + stop = (ssize_t)strlen(ceiling); + else + stop = git_buf_len(path); + } + scan = git_buf_len(path); + + iter.ptr = path->ptr; + iter.size = git_buf_len(path); + iter.asize = path->asize; + + while (scan >= stop) { + if ((error = cb(data, &iter)) < 0) + break; + iter.ptr[scan] = oldc; + scan = git_buf_rfind_next(&iter, '/'); + if (scan >= 0) { + scan++; + oldc = iter.ptr[scan]; + iter.size = scan; + iter.ptr[scan] = '\0'; + } + } + + if (scan >= 0) + iter.ptr[scan] = oldc; + + return error; +} + +bool git_path_exists(const char *path) +{ + assert(path); + return p_access(path, F_OK) == 0; +} + +bool git_path_isdir(const char *path) +{ + struct stat st; + if (p_stat(path, &st) < 0) + return false; + + return S_ISDIR(st.st_mode) != 0; +} + +bool git_path_isfile(const char *path) +{ + struct stat st; + + assert(path); + if (p_stat(path, &st) < 0) + return false; + + return S_ISREG(st.st_mode) != 0; +} + +int git_path_lstat(const char *path, struct stat *st) +{ + int err = 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 bool _check_dir_contents( + git_buf *dir, + const char *sub, + bool (*predicate)(const char *)) +{ + bool result; + size_t dir_size = git_buf_len(dir); + size_t sub_size = strlen(sub); + + /* 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); + + result = predicate(dir->ptr); + + /* restore path */ + git_buf_truncate(dir, dir_size); + return result; +} + +bool git_path_contains(git_buf *dir, const char *item) +{ + return _check_dir_contents(dir, item, &git_path_exists); +} + +bool git_path_contains_dir(git_buf *base, const char *subdir) +{ + return _check_dir_contents(base, subdir, &git_path_isdir); +} + +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; + + if (base != NULL && git_path_root(path) < 0) + error = git_buf_joinpath(dir, base, path); + else + error = git_buf_sets(dir, path); + + 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_path_isdir(dir->ptr) == false) + error = git_path_dirname_r(dir, dir->ptr); + + if (!error) + error = git_path_to_dir(dir); + + return error; +} + +int git_path_cmp( + const char *name1, size_t len1, int isdir1, + const char *name2, size_t len2, int isdir2) +{ + 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] - '/'); + if (len1 > len2) + return (!isdir1 && !isdir2) ? 1 : + (isdir2 ? name1[len2] - '/' : '/' - name1[len2]); + return 0; +} + +/* Taken from git.git */ +GIT_INLINE(int) is_dot_or_dotdot(const char *name) +{ + return (name[0] == '.' && + (name[1] == '\0' || + (name[1] == '.' && name[2] == '\0'))); +} + +int git_path_direach( + git_buf *path, + int (*fn)(void *, git_buf *), + void *arg) +{ + ssize_t wd_len; + DIR *dir; + struct dirent *de, *de_buf; + + if (git_path_to_dir(path) < 0) + return -1; + + wd_len = git_buf_len(path); + + if ((dir = opendir(path->ptr)) == NULL) { + giterr_set(GITERR_OS, "Failed to open directory '%s'", path->ptr); + return -1; + } + +#ifdef __sun + de_buf = git__malloc(sizeof(struct dirent) + FILENAME_MAX + 1); +#else + de_buf = git__malloc(sizeof(struct dirent)); +#endif + + while (p_readdir_r(dir, de_buf, &de) == 0 && de != NULL) { + int result; + + if (is_dot_or_dotdot(de->d_name)) + continue; + + if (git_buf_puts(path, de->d_name) < 0) { + closedir(dir); + git__free(de_buf); + return -1; + } + + result = fn(arg, path); + + git_buf_truncate(path, wd_len); /* restore path */ + + if (result < 0) { + closedir(dir); + git__free(de_buf); + return -1; + } + } + + closedir(dir); + git__free(de_buf); + return 0; +} + +int git_path_dirload( + const char *path, + size_t prefix_len, + size_t alloc_extra, + git_vector *contents) +{ + int error, need_slash; + DIR *dir; + struct dirent *de, *de_buf; + size_t path_len; + + assert(path != NULL && contents != NULL); + path_len = strlen(path); + assert(path_len > 0 && path_len >= prefix_len); + + if ((dir = opendir(path)) == NULL) { + giterr_set(GITERR_OS, "Failed to open directory '%s'", path); + return -1; + } + +#ifdef __sun + de_buf = git__malloc(sizeof(struct dirent) + FILENAME_MAX + 1); +#else + de_buf = git__malloc(sizeof(struct dirent)); +#endif + + path += prefix_len; + path_len -= prefix_len; + need_slash = (path_len > 0 && path[path_len-1] != '/') ? 1 : 0; + + while ((error = p_readdir_r(dir, de_buf, &de)) == 0 && de != NULL) { + char *entry_path; + size_t entry_len; + + if (is_dot_or_dotdot(de->d_name)) + continue; + + entry_len = strlen(de->d_name); + + entry_path = git__malloc( + path_len + need_slash + entry_len + 1 + alloc_extra); + GITERR_CHECK_ALLOC(entry_path); + + if (path_len) + memcpy(entry_path, path, path_len); + if (need_slash) + entry_path[path_len] = '/'; + memcpy(&entry_path[path_len + need_slash], de->d_name, entry_len); + entry_path[path_len + need_slash + entry_len] = '\0'; + + if (git_vector_insert(contents, entry_path) < 0) { + closedir(dir); + git__free(de_buf); + return -1; + } + } + + closedir(dir); + git__free(de_buf); + + if (error != 0) + giterr_set(GITERR_OS, "Failed to process directory entry in '%s'", path); + + return error; +} + +int git_path_with_stat_cmp(const void *a, const void *b) +{ + const git_path_with_stat *psa = a, *psb = b; + return git__strcmp_cb(psa->path, psb->path); +} + +int git_path_dirload_with_stat( + const char *path, + size_t prefix_len, + git_vector *contents) +{ + int error; + unsigned int i; + git_path_with_stat *ps; + git_buf full = GIT_BUF_INIT; + + if (git_buf_set(&full, path, prefix_len) < 0) + return -1; + + error = git_path_dirload( + path, prefix_len, sizeof(git_path_with_stat) + 1, contents); + if (error < 0) { + git_buf_free(&full); + return error; + } + + git_vector_foreach(contents, i, ps) { + size_t path_len = strlen((char *)ps); + + memmove(ps->path, ps, path_len + 1); + ps->path_len = path_len; + + 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)) { + ps->path[path_len] = '/'; + ps->path[path_len + 1] = '\0'; + } + } + + git_buf_free(&full); + + return error; +} diff --git a/src/path.h b/src/path.h new file mode 100644 index 000000000..fd76805e5 --- /dev/null +++ b/src/path.h @@ -0,0 +1,278 @@ +/* + * 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_path_h__ +#define INCLUDE_path_h__ + +#include "common.h" +#include "buffer.h" +#include "vector.h" + +/** + * Path manipulation utils + * + * These are path utilities that munge paths without actually + * looking at the real filesystem. + */ + +/* + * The dirname() function shall take a pointer to a character string + * that contains a pathname, and return a pointer to a string that is a + * pathname of the parent directory of that file. Trailing '/' characters + * in the path are not counted as part of the path. + * + * If path does not contain a '/', then dirname() shall return a pointer to + * the string ".". If path is a null pointer or points to an empty string, + * dirname() shall return a pointer to the string "." . + * + * The `git_path_dirname` implementation is thread safe. The returned + * string must be manually free'd. + * + * The `git_path_dirname_r` implementation writes the dirname to a `git_buf` + * if the buffer pointer is not NULL. + * It returns an error code < 0 if there is an allocation error, otherwise + * the length of the dirname (which will be > 0). + */ +extern char *git_path_dirname(const char *path); +extern int git_path_dirname_r(git_buf *buffer, const char *path); + +/* + * This function returns the basename of the file, which is the last + * part of its full name given by fname, with the drive letter and + * leading directories stripped off. For example, the basename of + * c:/foo/bar/file.ext is file.ext, and the basename of a:foo is foo. + * + * Trailing slashes and backslashes are significant: the basename of + * c:/foo/bar/ is an empty string after the rightmost slash. + * + * The `git_path_basename` implementation is thread safe. The returned + * string must be manually free'd. + * + * The `git_path_basename_r` implementation writes the basename to a `git_buf`. + * It returns an error code < 0 if there is an allocation error, otherwise + * the length of the basename (which will be >= 0). + */ +extern char *git_path_basename(const char *path); +extern int git_path_basename_r(git_buf *buffer, const char *path); + +extern const char *git_path_topdir(const char *path); + +/** + * Find offset to root of path if path has one. + * + * This will return a number >= 0 which is the offset to the start of the + * path, if the path is rooted (i.e. "/rooted/path" returns 0 and + * "c:/windows/rooted/path" returns 2). If the path is not rooted, this + * returns < 0. + */ +extern int git_path_root(const char *path); + +/** + * Ensure path has a trailing '/'. + */ +extern int git_path_to_dir(git_buf *path); + +/** + * Ensure string has a trailing '/' if there is space for it. + */ +extern void git_path_string_to_dir(char* path, size_t size); + +#ifdef GIT_WIN32 +/** + * Convert backslashes in path to forward slashes. + */ +GIT_INLINE(void) git_path_mkposix(char *path) +{ + while (*path) { + if (*path == '\\') + *path = '/'; + + path++; + } +} +#else +# define git_path_mkposix(p) /* blank */ +#endif + +extern int git__percent_decode(git_buf *decoded_out, const char *input); + +/** + * Extract path from file:// URL. + */ +extern int git_path_fromurl(git_buf *local_path_out, const char *file_url); + + +/** + * Path filesystem utils + * + * These are path utilities that actually access the filesystem. + */ + +/** + * Check if a file exists and can be accessed. + * @return true or false + */ +extern bool git_path_exists(const char *path); + +/** + * Check if the given path points to a directory. + * @return true or false + */ +extern bool git_path_isdir(const char *path); + +/** + * Check if the given path points to a regular file. + * @return true or false + */ +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 0 if item exists in directory, <0 otherwise. + */ +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 + * @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 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 + * @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 bool git_path_contains_file(git_buf *dir, const char *file); + +/** + * Clean up path, prepending base if it is not already rooted. + */ +extern int git_path_prettify(git_buf *path_out, const char *path, const char *base); + +/** + * Clean up path, prepending base if it is not already rooted and + * appending a slash. + */ +extern int git_path_prettify_dir(git_buf *path_out, const char *path, const char *base); + +/** + * Get a directory from a path. + * + * If path is a directory, this acts like `git_path_prettify_dir` + * (cleaning up path and appending a '/'). If path is a normal file, + * this prettifies it, then removed the filename a la dirname and + * appends the trailing '/'. If the path does not exist, it is + * treated like a regular filename. + */ +extern int git_path_find_dir(git_buf *dir, const char *path, const char *base); + +/** + * Walk each directory entry, except '.' and '..', calling fn(state). + * + * @param pathbuf buffer the function reads the initial directory + * path from, and updates with each successive entry's name. + * @param fn function to invoke with each entry. The first arg is + * the input state and the second arg is pathbuf. The function + * may modify the pathbuf, but only by appending new text. + * @param state to pass to fn as the first arg. + */ +extern int git_path_direach( + git_buf *pathbuf, + int (*fn)(void *, git_buf *), + void *state); + +/** + * Sort function to order two paths. + */ +extern int git_path_cmp( + 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 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 + * and updates with each successive name. + * @param ceiling Prefix of path at which to stop walking up. If NULL, + * this will walk all the way up to the root. If not a prefix of + * pathbuf, the callback will be invoked a single time on the + * original input path. + * @param fn Function to invoke on each path. The first arg is the + * input satte and the second arg is the pathbuf. The function + * should not modify the pathbuf. + * @param state Passed to fn as the first ath. + */ +extern int git_path_walk_up( + git_buf *pathbuf, + const char *ceiling, + int (*fn)(void *state, git_buf *), + void *state); + +/** + * Load all directory entries (except '.' and '..') into a vector. + * + * For cases where `git_path_direach()` is not appropriate, this + * allows you to load the filenames in a directory into a vector + * of strings. That vector can then be sorted, iterated, or whatever. + * Remember to free alloc of the allocated strings when you are done. + * + * @param path The directory to read from. + * @param prefix_len When inserting entries, the trailing part of path + * will be prefixed after this length. I.e. given path "/a/b" and + * prefix_len 3, the entries will look like "b/e1", "b/e2", etc. + * @param alloc_extra Extra bytes to add to each string allocation in + * case you want to append anything funny. + * @param contents Vector to fill with directory entry names. + */ +extern int git_path_dirload( + const char *path, + size_t prefix_len, + size_t alloc_extra, + git_vector *contents); + + +typedef struct { + struct stat st; + size_t path_len; + char path[GIT_FLEX_ARRAY]; +} git_path_with_stat; + +extern int git_path_with_stat_cmp(const void *a, const void *b); + +/** + * Load all directory entries along with stat info into a vector. + * + * This is just like git_path_dirload except that each entry in the + * vector is a git_path_with_stat structure that contains both the + * path and the stat info, plus directories will have a / suffixed + * to their path name. + */ +extern int git_path_dirload_with_stat( + const char *path, + size_t prefix_len, + git_vector *contents); + +#endif diff --git a/src/pkt.c b/src/pkt.c new file mode 100644 index 000000000..88510f4b1 --- /dev/null +++ b/src/pkt.c @@ -0,0 +1,355 @@ +/* + * 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 "git2/types.h" +#include "git2/errors.h" +#include "git2/refs.h" +#include "git2/revwalk.h" + +#include "pkt.h" +#include "util.h" +#include "netops.h" +#include "posix.h" +#include "buffer.h" + +#include <ctype.h> + +#define PKT_LEN_SIZE 4 +static const char pkt_done_str[] = "0009done\n"; +static const char pkt_flush_str[] = "0000"; +static const char pkt_have_prefix[] = "0032have "; +static const char pkt_want_prefix[] = "0032want "; + +static int flush_pkt(git_pkt **out) +{ + git_pkt *pkt; + + pkt = git__malloc(sizeof(git_pkt)); + GITERR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_FLUSH; + *out = pkt; + + return 0; +} + +/* the rest of the line will be useful for multi_ack */ +static int ack_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt *pkt; + GIT_UNUSED(line); + GIT_UNUSED(len); + + pkt = git__malloc(sizeof(git_pkt)); + GITERR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_ACK; + *out = pkt; + + return 0; +} + +static int nak_pkt(git_pkt **out) +{ + git_pkt *pkt; + + pkt = git__malloc(sizeof(git_pkt)); + GITERR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_NAK; + *out = pkt; + + return 0; +} + +static int pack_pkt(git_pkt **out) +{ + git_pkt *pkt; + + pkt = git__malloc(sizeof(git_pkt)); + GITERR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_PACK; + *out = pkt; + + return 0; +} + +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); + GITERR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_COMMENT; + memcpy(pkt->comment, line, len); + pkt->comment[len] = '\0'; + + *out = (git_pkt *) pkt; + + 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; +} + +/* + * Parse an other-ref line. + */ +static int ref_pkt(git_pkt **out, const char *line, size_t len) +{ + int error; + git_pkt_ref *pkt; + + pkt = git__malloc(sizeof(git_pkt_ref)); + GITERR_CHECK_ALLOC(pkt); + + memset(pkt, 0x0, sizeof(git_pkt_ref)); + pkt->type = GIT_PKT_REF; + if ((error = git_oid_fromstr(&pkt->head.oid, line)) < 0) + goto error_out; + + /* Check for a bit of consistency */ + if (line[GIT_OID_HEXSZ] != ' ') { + giterr_set(GITERR_NET, "Error parsing pkt-line"); + error = -1; + goto error_out; + } + + /* Jump from the name */ + line += GIT_OID_HEXSZ + 1; + len -= (GIT_OID_HEXSZ + 1); + + if (line[len - 1] == '\n') + --len; + + pkt->head.name = git__malloc(len + 1); + GITERR_CHECK_ALLOC(pkt->head.name); + + memcpy(pkt->head.name, line, len); + pkt->head.name[len] = '\0'; + + if (strlen(pkt->head.name) < len) { + pkt->capabilities = strchr(pkt->head.name, '\0') + 1; + } + + *out = (git_pkt *)pkt; + return 0; + +error_out: + git__free(pkt); + return error; +} + +static int32_t parse_len(const char *line) +{ + char num[PKT_LEN_SIZE + 1]; + int i, error; + 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])) { + giterr_set(GITERR_NET, "Found invalid hex digit in length"); + return -1; + } + } + + if ((error = git__strtol32(&len, num, &num_end, 16)) < 0) + return error; + + return len; +} + +/* + * As per the documentation, the syntax is: + * + * pkt-line = data-pkt / flush-pkt + * data-pkt = pkt-len pkt-payload + * pkt-len = 4*(HEXDIG) + * pkt-payload = (pkt-len -4)*(OCTET) + * flush-pkt = "0000" + * + * Which means that the first four bytes are the length of the line, + * in ASCII hexadecimal (including itself) + */ + +int git_pkt_parse_line( + git_pkt **head, const char *line, const char **out, size_t bufflen) +{ + int ret; + int32_t len; + + /* Not even enough for the length */ + if (bufflen > 0 && bufflen < PKT_LEN_SIZE) + return GIT_EBUFS; + + 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. + */ + if (bufflen >= 4 && !git__prefixcmp(line, "PACK")) { + giterr_clear(); + *out = line; + return pack_pkt(head); + } + + return (int)len; + } + + /* + * If we were given a buffer length, then make sure there is + * enough in the buffer to satisfy this line + */ + if (bufflen > 0 && bufflen < (size_t)len) + return GIT_EBUFS; + + line += PKT_LEN_SIZE; + /* + * TODO: How do we deal with empty lines? Try again? with the next + * line? + */ + if (len == PKT_LEN_SIZE) { + *out = line; + return 0; + } + + if (len == 0) { /* Flush pkt */ + *out = line; + return flush_pkt(head); + } + + len -= PKT_LEN_SIZE; /* the encoded length includes its own size */ + + /* Assming the minimal size is actually 4 */ + if (!git__prefixcmp(line, "ACK")) + ret = ack_pkt(head, line, len); + else if (!git__prefixcmp(line, "NAK")) + ret = nak_pkt(head); + else if (!git__prefixcmp(line, "ERR ")) + ret = err_pkt(head, line, len); + else if (*line == '#') + ret = comment_pkt(head, line, len); + else + ret = ref_pkt(head, line, len); + + *out = line + len; + + return ret; +} + +void git_pkt_free(git_pkt *pkt) +{ + if (pkt->type == GIT_PKT_REF) { + git_pkt_ref *p = (git_pkt_ref *) pkt; + git__free(p->head.name); + } + + git__free(pkt); +} + +int git_pkt_buffer_flush(git_buf *buf) +{ + return git_buf_put(buf, pkt_flush_str, strlen(pkt_flush_str)); +} + +static int buffer_want_with_caps(git_remote_head *head, git_transport_caps *caps, git_buf *buf) +{ + char capstr[20]; + char oid[GIT_OID_HEXSZ +1] = {0}; + unsigned int len; + + if (caps->ofs_delta) + strncpy(capstr, GIT_CAP_OFS_DELTA, sizeof(capstr)); + + 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); +} + +/* + * All "want" packets have the same length and format, so what we do + * is overwrite the OID each time. + */ + +int git_pkt_buffer_wants(const git_vector *refs, git_transport_caps *caps, git_buf *buf) +{ + unsigned int i = 0; + git_remote_head *head; + + if (caps->common) { + for (; i < refs->length; ++i) { + head = refs->contents[i]; + if (!head->local) + break; + } + + if (buffer_want_with_caps(refs->contents[i], caps, buf) < 0) + return -1; + + i++; + } + + for (; i < refs->length; ++i) { + char oid[GIT_OID_HEXSZ]; + + head = refs->contents[i]; + if (head->local) + continue; + + git_oid_fmt(oid, &head->oid); + 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_buffer_have(git_oid *oid, git_buf *buf) +{ + char oidhex[GIT_OID_HEXSZ + 1]; + + memset(oidhex, 0x0, sizeof(oidhex)); + git_oid_fmt(oidhex, oid); + return git_buf_printf(buf, "%s%s\n", pkt_have_prefix, oidhex); +} + +int git_pkt_buffer_done(git_buf *buf) +{ + return git_buf_puts(buf, pkt_done_str); +} diff --git a/src/pkt.h b/src/pkt.h new file mode 100644 index 000000000..75442c833 --- /dev/null +++ b/src/pkt.h @@ -0,0 +1,81 @@ +/* + * 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_pkt_h__ +#define INCLUDE_pkt_h__ + +#include "common.h" +#include "transport.h" +#include "buffer.h" +#include "posix.h" +#include "git2/net.h" + +enum git_pkt_type { + GIT_PKT_CMD, + GIT_PKT_FLUSH, + GIT_PKT_REF, + GIT_PKT_HAVE, + GIT_PKT_ACK, + GIT_PKT_NAK, + GIT_PKT_PACK, + GIT_PKT_COMMENT, + GIT_PKT_ERR, +}; + +/* Used for multi-ack */ +enum git_ack_status { + GIT_ACK_NONE, + GIT_ACK_CONTINUE, + GIT_ACK_COMMON, + GIT_ACK_READY +}; + +/* This would be a flush pkt */ +typedef struct { + enum git_pkt_type type; +} git_pkt; + +struct git_pkt_cmd { + enum git_pkt_type type; + char *cmd; + char *path; + char *host; +}; + +/* This is a pkt-line with some info in it */ +typedef struct { + enum git_pkt_type type; + git_remote_head head; + char *capabilities; +} git_pkt_ref; + +/* Useful later */ +typedef struct { + enum git_pkt_type type; + git_oid oid; + enum git_ack_status status; +} git_pkt_ack; + +typedef struct { + enum git_pkt_type type; + 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(GIT_SOCKET s); +int git_pkt_buffer_done(git_buf *buf); +int git_pkt_buffer_wants(const git_vector *refs, git_transport_caps *caps, git_buf *buf); +int git_pkt_buffer_have(git_oid *oid, git_buf *buf); +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 new file mode 100644 index 000000000..a9a6af984 --- /dev/null +++ b/src/posix.c @@ -0,0 +1,116 @@ +/* + * 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 "posix.h" +#include "path.h" +#include <stdio.h> +#include <ctype.h> + +#ifndef GIT_WIN32 + +int p_open(const char *path, int flags, ...) +{ + mode_t mode = 0; + + if (flags & O_CREAT) + { + va_list arg_list; + + va_start(arg_list, flags); + mode = (mode_t)va_arg(arg_list, int); + va_end(arg_list); + } + + return open(path, flags | O_BINARY, mode); +} + +int p_creat(const char *path, mode_t mode) +{ + return open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, mode); +} + +int p_getcwd(char *buffer_out, size_t size) +{ + char *cwd_buffer; + + assert(buffer_out && size > 0); + + cwd_buffer = getcwd(buffer_out, size); + + if (cwd_buffer == NULL) + return -1; + + git_path_mkposix(buffer_out); + git_path_string_to_dir(buffer_out, size); /* append trailing slash */ + + return 0; +} + +int p_rename(const char *from, const char *to) +{ + if (!link(from, to)) { + p_unlink(from); + return 0; + } + + if (!rename(from, to)) + return 0; + + return -1; +} + +#endif + +int p_read(git_file fd, void *buf, size_t cnt) +{ + char *b = buf; + while (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 -1; + } + if (!r) + break; + cnt -= r; + b += r; + } + return (int)(b - (char *)buf); +} + +int p_write(git_file fd, const void *buf, size_t cnt) +{ + const char *b = buf; + while (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 -1; + } + if (!r) { + errno = EPIPE; + return -1; + } + cnt -= r; + b += r; + } + return 0; +} diff --git a/src/posix.h b/src/posix.h new file mode 100644 index 000000000..d020d94ac --- /dev/null +++ b/src/posix.h @@ -0,0 +1,79 @@ +/* + * 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_posix_h__ +#define INCLUDE_posix_h__ + +#include "common.h" +#include <fcntl.h> +#include <time.h> + +#define S_IFGITLINK 0160000 +#define S_ISGITLINK(m) (((m) & S_IFMT) == S_IFGITLINK) + +#if !defined(O_BINARY) +#define O_BINARY 0 +#endif + +typedef int git_file; + +/** + * Standard POSIX Methods + * + * All the methods starting with the `p_` prefix are + * direct ports of the standard POSIX methods. + * + * Some of the methods are slightly wrapped to provide + * saner defaults. Some of these methods are emulated + * in Windows platforns. + * + * Use your manpages to check the docs on these. + * Straightforward + */ + +extern int p_read(git_file fd, void *buf, size_t cnt); +extern int p_write(git_file fd, const void *buf, size_t cnt); + +#define p_fstat(f,b) fstat(f, b) +#define p_lseek(f,n,w) lseek(f, n, w) +#define p_close(fd) close(fd) +#define p_umask(m) umask(m) + +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); + +#ifndef GIT_WIN32 + +#define p_stat(p,b) stat(p, b) +#define p_chdir(p) chdir(p) +#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 + +/** + * Platform-dependent methods + */ +#ifdef GIT_WIN32 +# include "win32/posix.h" +#else +# include "unix/posix.h" +#endif + +#define p_readdir_r(d,e,r) readdir_r(d,e,r) + +#endif diff --git a/src/ppc/sha1.c b/src/ppc/sha1.c index ec6a1926d..803b81d0a 100644 --- a/src/ppc/sha1.c +++ b/src/ppc/sha1.c @@ -1,17 +1,15 @@ /* - * SHA-1 implementation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * Copyright (C) 2005 Paul Mackerras <paulus@samba.org> - * - * This version assumes we are running on a big-endian machine. - * It calls an external sha1_core() to process blocks of 64 bytes. + * 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 <stdio.h> #include <string.h> #include "sha1.h" extern void ppc_sha1_core(uint32_t *hash, const unsigned char *p, - unsigned int nblocks); + unsigned int nblocks); int ppc_SHA1_Init(ppc_SHA_CTX *c) { diff --git a/src/ppc/sha1.h b/src/ppc/sha1.h index 70957110c..aca4e5dda 100644 --- a/src/ppc/sha1.h +++ b/src/ppc/sha1.h @@ -1,7 +1,8 @@ /* - * SHA-1 implementation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * Copyright (C) 2005 Paul Mackerras <paulus@samba.org> + * 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 <stdint.h> diff --git a/src/pqueue.c b/src/pqueue.c index 6307175e3..cb59c13ec 100644 --- a/src/pqueue.c +++ b/src/pqueue.c @@ -1,52 +1,36 @@ /* - * BORING COPYRIGHT NOTICE: + * Copyright (C) 2009-2012 the libgit2 contributors * - * This file is a heavily modified version of the priority queue found - * in the Apache project and the libpqueue library. - * - * https://github.com/vy/libpqueue - * - * These are the original authors: - * - * Copyright 2010 Volkan Yazıcı <volkan.yazici@gmail.com> - * Copyright 2006-2010 The Apache Software Foundation - * - * This file is licensed under the Apache 2.0 license, which - * supposedly makes it compatible with the GPLv2 that libgit2 uses. - * - * Check the Apache license at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * So much licensing trouble for a binary heap. Oh well. + * 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 "pqueue.h" -#define left(i) ((i) << 1) -#define right(i) (((i) << 1) + 1) +#define left(i) ((i) << 1) +#define right(i) (((i) << 1) + 1) #define parent(i) ((i) >> 1) 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 = malloc((n + 1) * sizeof(void *))) == NULL) - return GIT_ENOMEM; + /* Need to allocate n+1 elements since element 0 isn't used. */ + 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; + q->size = 1; + q->avail = q->step = (n + 1); /* see comment above about n+1 */ + q->cmppri = cmppri; - return GIT_SUCCESS; + return 0; } void git_pqueue_free(git_pqueue *q) { - free(q->d); + git__free(q->d); q->d = NULL; } @@ -57,101 +41,101 @@ void git_pqueue_clear(git_pqueue *q) size_t git_pqueue_size(git_pqueue *q) { - /* queue element 0 exists but doesn't count since it isn't used. */ - return (q->size - 1); + /* queue element 0 exists but doesn't count since it isn't used. */ + return (q->size - 1); } static void bubble_up(git_pqueue *q, size_t i) { - size_t parent_node; - void *moving_node = q->d[i]; + size_t parent_node; + void *moving_node = q->d[i]; - for (parent_node = parent(i); - ((i > 1) && q->cmppri(q->d[parent_node], moving_node)); - i = parent_node, parent_node = parent(i)) { - q->d[i] = q->d[parent_node]; - } + for (parent_node = parent(i); + ((i > 1) && q->cmppri(q->d[parent_node], moving_node)); + i = parent_node, parent_node = parent(i)) { + q->d[i] = q->d[parent_node]; + } - q->d[i] = moving_node; + q->d[i] = moving_node; } static size_t maxchild(git_pqueue *q, size_t i) { - size_t child_node = left(i); + size_t child_node = left(i); - if (child_node >= q->size) - return 0; + if (child_node >= q->size) + return 0; - if ((child_node + 1) < q->size && - q->cmppri(q->d[child_node], q->d[child_node + 1])) - child_node++; /* use right child instead of left */ + if ((child_node + 1) < q->size && + q->cmppri(q->d[child_node], q->d[child_node + 1])) + child_node++; /* use right child instead of left */ - return child_node; + return child_node; } static void percolate_down(git_pqueue *q, size_t i) { - size_t child_node; - void *moving_node = q->d[i]; + size_t child_node; + void *moving_node = q->d[i]; - while ((child_node = maxchild(q, i)) != 0 && - q->cmppri(moving_node, q->d[child_node])) { - q->d[i] = q->d[child_node]; - i = child_node; - } + while ((child_node = maxchild(q, i)) != 0 && + q->cmppri(moving_node, q->d[child_node])) { + q->d[i] = q->d[child_node]; + i = child_node; + } - q->d[i] = moving_node; + q->d[i] = moving_node; } int git_pqueue_insert(git_pqueue *q, void *d) { - void *tmp; - size_t i; - size_t newsize; + void *tmp; + size_t i; + size_t newsize; - if (!q) return 1; + if (!q) return 1; - /* allocate more memory if necessary */ - if (q->size >= q->avail) { - newsize = q->size + q->step; - if ((tmp = realloc(q->d, sizeof(void *) * newsize)) == NULL) - return GIT_ENOMEM; + /* allocate more memory if necessary */ + if (q->size >= q->avail) { + newsize = q->size + q->step; + tmp = git__realloc(q->d, sizeof(void *) * newsize); + GITERR_CHECK_ALLOC(tmp); - q->d = tmp; - q->avail = newsize; - } + q->d = tmp; + q->avail = newsize; + } - /* insert item */ - i = q->size++; - q->d[i] = d; - bubble_up(q, i); + /* insert item */ + i = q->size++; + q->d[i] = d; + bubble_up(q, i); - return GIT_SUCCESS; + return 0; } void *git_pqueue_pop(git_pqueue *q) { - void *head; + void *head; - if (!q || q->size == 1) - return NULL; + if (!q || q->size == 1) + return NULL; - head = q->d[1]; - q->d[1] = q->d[--q->size]; - percolate_down(q, 1); + head = q->d[1]; + q->d[1] = q->d[--q->size]; + percolate_down(q, 1); - return head; + return head; } void *git_pqueue_peek(git_pqueue *q) { - if (!q || q->size == 1) - return NULL; - return q->d[1]; + if (!q || q->size == 1) + return NULL; + return q->d[1]; } diff --git a/src/pqueue.h b/src/pqueue.h index 7a1394803..a3e1edd1d 100644 --- a/src/pqueue.h +++ b/src/pqueue.h @@ -1,24 +1,8 @@ /* - * BORING COPYRIGHT NOTICE: + * Copyright (C) 2009-2012 the libgit2 contributors * - * This file is a heavily modified version of the priority queue found - * in the Apache project and the libpqueue library. - * - * https://github.com/vy/libpqueue - * - * These are the original authors: - * - * Copyright 2010 Volkan Yazıcı <volkan.yazici@gmail.com> - * Copyright 2006-2010 The Apache Software Foundation - * - * This file is licensed under the Apache 2.0 license, which - * supposedly makes it compatible with the GPLv2 that libgit2 uses. - * - * Check the Apache license at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * So much licensing trouble for a binary heap. Oh well. + * 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_pqueue_h__ @@ -29,9 +13,9 @@ typedef int (*git_pqueue_cmp)(void *a, void *b); /** the priority queue handle */ typedef struct { - size_t size, avail, step; - git_pqueue_cmp cmppri; - void **d; + size_t size, avail, step; + git_pqueue_cmp cmppri; + void **d; } git_pqueue; @@ -39,7 +23,7 @@ typedef struct { * initialize the queue * * @param n the initial estimate of the number of queue items for which memory - * should be preallocated + * should be preallocated * @param cmppri the callback function to compare two nodes of the queue * * @Return the handle or NULL for insufficent memory diff --git a/src/protocol.c b/src/protocol.c new file mode 100644 index 000000000..6b3861796 --- /dev/null +++ b/src/protocol.c @@ -0,0 +1,58 @@ +/* + * 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 "protocol.h" +#include "pkt.h" +#include "buffer.h" + +int git_protocol_store_refs(git_protocol *p, const char *data, size_t len) +{ + git_buf *buf = &p->buf; + git_vector *refs = p->refs; + int error; + const char *line_end, *ptr; + + if (len == 0) { /* EOF */ + 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); + ptr = buf->ptr; + while (1) { + git_pkt *pkt; + + if (git_buf_len(buf) == 0) + return 0; + + error = git_pkt_parse_line(&pkt, ptr, &line_end, git_buf_len(buf)); + if (error == GIT_EBUFS) + return 0; /* Ask for more */ + if (error < 0) + return p->error = -1; + + git_buf_consume(buf, line_end); + + 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 0; +} diff --git a/src/protocol.h b/src/protocol.h new file mode 100644 index 000000000..a6c3e0735 --- /dev/null +++ b/src/protocol.h @@ -0,0 +1,23 @@ +/* + * 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_protocol_h__ +#define INCLUDE_protocol_h__ + +#include "transport.h" +#include "buffer.h" + +typedef struct { + git_transport *transport; + git_vector *refs; + git_buf buf; + int error; + unsigned int flush :1; +} git_protocol; + +int git_protocol_store_refs(git_protocol *p, const char *data, size_t len); + +#endif diff --git a/src/reflog.c b/src/reflog.c new file mode 100644 index 000000000..3ea073e65 --- /dev/null +++ b/src/reflog.c @@ -0,0 +1,340 @@ +/* + * 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 "reflog.h" +#include "repository.h" +#include "filebuf.h" +#include "signature.h" + +static int reflog_init(git_reflog **reflog, git_reference *ref) +{ + git_reflog *log; + + *reflog = NULL; + + 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 -1; + } + + *reflog = log; + + return 0; +} + +static int reflog_write(const char *log_path, const char *oid_old, + const char *oid_new, const git_signature *committer, + const char *msg) +{ + 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, ' '); + + git_buf_puts(&log, oid_new); + + git_signature__writebuf(&log, " ", committer); + git_buf_truncate(&log, log.size - 1); /* drop LF */ + + if (msg) { + git_buf_putc(&log, '\t'); + git_buf_puts(&log, msg); + } + + if (!trailing_newline) + git_buf_putc(&log, '\n'); + + if (git_buf_oom(&log)) { + git_buf_free(&log); + return -1; + } + + 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_buf_free(&log); + + return error; +} + +static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) +{ + const char *ptr; + git_reflog_entry *entry; + +#define seek_forward(_increase) do { \ + if (_increase >= buf_size) { \ + 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)); + GITERR_CHECK_ALLOC(entry); + + 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) < 0) + goto fail; + seek_forward(GIT_OID_HEXSZ + 1); + + ptr = buf; + + /* Seek forward to the end of the signature. */ + while (*buf && *buf != '\t' && *buf != '\n') + seek_forward(1); + + 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. */ + seek_forward(1); + ptr = buf; + + while (*buf && *buf != '\n') + 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 (git_vector_insert(&log->entries, entry) < 0) + goto fail; + } + + return 0; + +#undef seek_forward + +fail: + if (entry) { + git__free(entry->committer); + git__free(entry); + } + return -1; +} + +void git_reflog_free(git_reflog *reflog) +{ + unsigned int i; + git_reflog_entry *entry; + + for (i=0; i < reflog->entries.length; i++) { + entry = git_vector_get(&reflog->entries, i); + + git_signature_free(entry->committer); + + git__free(entry->msg); + git__free(entry); + } + + git_vector_free(&reflog->entries); + git__free(reflog->ref_name); + git__free(reflog); +} + +int git_reflog_read(git_reflog **reflog, git_reference *ref) +{ + int error; + git_buf log_path = GIT_BUF_INIT; + git_buf log_file = GIT_BUF_INIT; + git_reflog *log = NULL; + + *reflog = NULL; + + 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) + error = git_futils_readbuffer(&log_file, log_path.ptr); + + if (!error) + error = reflog_parse(log, log_file.ptr, log_file.size); + + if (!error) + *reflog = log; + else + git_reflog_free(log); + + git_buf_free(&log_file); + git_buf_free(&log_path); + + return error; +} + +int git_reflog_write(git_reference *ref, const git_oid *oid_old, + const git_signature *committer, const char *msg) +{ + int error; + char old[GIT_OID_HEXSZ+1]; + char new[GIT_OID_HEXSZ+1]; + git_buf log_path = GIT_BUF_INIT; + git_reference *r; + const git_oid *oid; + + if ((error = git_reference_resolve(&r, ref)) < 0) + return error; + + oid = git_reference_oid(r); + if (oid == NULL) { + giterr_set(GITERR_REFERENCE, + "Failed to write reflog. Cannot resolve reference `%s`", r->name); + git_reference_free(r); + return -1; + } + + git_oid_tostr(new, GIT_OID_HEXSZ+1, oid); + + git_reference_free(r); + + error = git_buf_join_n(&log_path, '/', 3, + ref->owner->path_repository, GIT_REFLOG_DIR, ref->name); + if (error < 0) + goto cleanup; + + if (git_path_exists(log_path.ptr) == false) { + error = git_futils_mkpath2file(log_path.ptr, GIT_REFLOG_DIR_MODE); + } 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) { + giterr_set(GITERR_REFERENCE, + "Failed to write reflog. Old OID cannot be NULL for existing reference"); + error = -1; + } + if (error < 0) + goto cleanup; + + if (oid_old) + git_oid_tostr(old, sizeof(old), oid_old); + else + p_snprintf(old, sizeof(old), "%0*d", GIT_OID_HEXSZ, 0); + + error = reflog_write(log_path.ptr, old, new, committer, msg); + +cleanup: + git_buf_free(&log_path); + return error; +} + +int git_reflog_rename(git_reference *ref, const char *new_name) +{ + int error; + 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)) + error = p_rename(git_buf_cstr(&old_path), git_buf_cstr(&new_path)); + else + error = -1; + + git_buf_free(&old_path); + git_buf_free(&new_path); + + return error; +} + +int git_reflog_delete(git_reference *ref) +{ + int error; + git_buf path = GIT_BUF_INIT; + + error = git_buf_join_n( + &path, '/', 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name); + + if (!error && git_path_exists(path.ptr)) + error = p_unlink(path.ptr); + + git_buf_free(&path); + + return error; +} + +unsigned int git_reflog_entrycount(git_reflog *reflog) +{ + assert(reflog); + return reflog->entries.length; +} + +const git_reflog_entry * git_reflog_entry_byindex(git_reflog *reflog, unsigned int idx) +{ + assert(reflog); + return git_vector_get(&reflog->entries, idx); +} + +const git_oid * git_reflog_entry_oidold(const git_reflog_entry *entry) +{ + assert(entry); + return &entry->oid_old; +} + +const git_oid * git_reflog_entry_oidnew(const git_reflog_entry *entry) +{ + assert(entry); + return &entry->oid_cur; +} + +git_signature * git_reflog_entry_committer(const git_reflog_entry *entry) +{ + assert(entry); + return entry->committer; +} + +char * git_reflog_entry_msg(const git_reflog_entry *entry) +{ + assert(entry); + return entry->msg; +} diff --git a/src/reflog.h b/src/reflog.h new file mode 100644 index 000000000..33cf0776c --- /dev/null +++ b/src/reflog.h @@ -0,0 +1,34 @@ +/* + * 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_reflog_h__ +#define INCLUDE_reflog_h__ + +#include "common.h" +#include "git2/reflog.h" +#include "vector.h" + +#define GIT_REFLOG_DIR "logs/" +#define GIT_REFLOG_DIR_MODE 0777 +#define GIT_REFLOG_FILE_MODE 0666 + +#define GIT_REFLOG_SIZE_MIN (2*GIT_OID_HEXSZ+2+17) + +struct git_reflog_entry { + git_oid oid_old; + git_oid oid_cur; + + git_signature *committer; + + char *msg; +}; + +struct git_reflog { + char *ref_name; + git_vector entries; +}; + +#endif /* INCLUDE_reflog_h__ */ diff --git a/src/refs.c b/src/refs.c index c21c9583d..1ef3e13a4 100644 --- a/src/refs.c +++ b/src/refs.c @@ -1,433 +1,332 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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 "refs.h" #include "hash.h" #include "repository.h" #include "fileops.h" +#include "pack.h" +#include "reflog.h" #include <git2/tag.h> #include <git2/object.h> -#define MAX_NESTING_LEVEL 5 - -typedef struct { - git_reference ref; - git_oid oid; - git_oid peel_target; -} reference_oid; +GIT__USE_STRMAP; -typedef struct { - git_reference ref; - char *target; -} reference_symbolic; +#define DEFAULT_NESTING_LEVEL 5 +#define MAX_NESTING_LEVEL 10 -static const int default_table_size = 32; - -static uint32_t reftable_hash(const void *key, int hash_id) -{ - static uint32_t hash_seeds[GIT_HASHTABLE_HASHES] = { - 2147483647, - 0x5d20bb23, - 0x7daaab3c - }; +enum { + GIT_PACKREF_HAS_PEEL = 1, + GIT_PACKREF_WAS_LOOSE = 2 +}; - return git__hash(key, strlen((const char *)key), hash_seeds[hash_id]); -} +struct packref { + git_oid oid; + git_oid peel; + char flags; + char name[GIT_FLEX_ARRAY]; +}; -static void reference_free(git_reference *reference); -static int reference_create(git_reference **ref_out, git_repository *repo, const char *name, git_rtype type); -static int reference_read(gitfo_buf *file_content, time_t *mtime, const char *repo_path, const char *ref_name); +static int reference_read( + git_buf *file_content, + time_t *mtime, + const char *repo_path, + const char *ref_name, + int *updated); /* loose refs */ -static int loose_parse_symbolic(git_reference *ref, gitfo_buf *file_content); -static int loose_parse_oid(git_reference *ref, gitfo_buf *file_content); -static int loose_lookup(git_reference **ref_out, git_repository *repo, const char *name, int skip_symbolic); +static int loose_parse_symbolic(git_reference *ref, git_buf *file_content); +static int loose_parse_oid(git_oid *ref, git_buf *file_content); +static int loose_lookup(git_reference *ref); +static int loose_lookup_to_packfile(struct packref **ref_out, + git_repository *repo, const char *name); static int loose_write(git_reference *ref); -static int loose_update(git_reference *ref); /* packed refs */ -static int packed_parse_peel(reference_oid *tag_ref, const char **buffer_out, const char *buffer_end); -static int packed_parse_oid(reference_oid **ref_out, git_repository *repo, const char **buffer_out, const char *buffer_end); +static int packed_parse_peel(struct packref *tag_ref, + const char **buffer_out, const char *buffer_end); +static int packed_parse_oid(struct packref **ref_out, + const char **buffer_out, const char *buffer_end); static int packed_load(git_repository *repo); static int packed_loadloose(git_repository *repository); -static int packed_write_ref(reference_oid *ref, git_filebuf *file); -static int packed_find_peel(reference_oid *ref); +static int packed_write_ref(struct packref *ref, git_filebuf *file); +static int packed_find_peel(git_repository *repo, struct packref *ref); static int packed_remove_loose(git_repository *repo, git_vector *packing_list); static int packed_sort(const void *a, const void *b); +static int packed_lookup(git_reference *ref); static int packed_write(git_repository *repo); /* internal helpers */ -static int reference_create_symbolic(git_reference **ref_out, git_repository *repo, const char *name, const char *target, int force); -static int reference_create_oid(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id, int force); -static int reference_rename(git_reference *ref, const char *new_name, int force); +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); /* name normalization */ -static int check_valid_ref_char(char ch); -static int normalize_name(char *buffer_out, const char *name, int is_oid_ref); +static int normalize_name(char *buffer_out, size_t out_size, + const char *name, int is_oid_ref); -/***************************************** - * Internal methods - Constructor/destructor - *****************************************/ -static void reference_free(git_reference *reference) + +void git_reference_free(git_reference *reference) { if (reference == NULL) return; - if (reference->name) - free(reference->name); + git__free(reference->name); + reference->name = NULL; - if (reference->type == GIT_REF_SYMBOLIC) - free(((reference_symbolic *)reference)->target); + if (reference->flags & GIT_REF_SYMBOLIC) { + git__free(reference->target.symbolic); + reference->target.symbolic = NULL; + } - free(reference); + git__free(reference); } -static int reference_create( +static int reference_alloc( git_reference **ref_out, git_repository *repo, - const char *name, - git_rtype type) + const char *name) { - char normalized[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH]; - int error = GIT_SUCCESS, size; git_reference *reference = NULL; assert(ref_out && repo && name); - if (type == GIT_REF_SYMBOLIC) - size = sizeof(reference_symbolic); - else if (type == GIT_REF_OID) - size = sizeof(reference_oid); - else - return git__throw(GIT_EINVALIDARGS, - "Invalid reference type. Use either GIT_REF_OID or GIT_REF_SYMBOLIC as type specifier"); - - reference = git__malloc(size); - if (reference == NULL) - return GIT_ENOMEM; + reference = git__malloc(sizeof(git_reference)); + GITERR_CHECK_ALLOC(reference); - memset(reference, 0x0, size); + memset(reference, 0x0, sizeof(git_reference)); reference->owner = repo; - reference->type = type; - error = normalize_name(normalized, name, (type & GIT_REF_OID)); - if (error < GIT_SUCCESS) - goto cleanup; - - reference->name = git__strdup(normalized); - if (reference->name == NULL) { - error = GIT_ENOMEM; - goto cleanup; - } + reference->name = git__strdup(name); + GITERR_CHECK_ALLOC(reference->name); *ref_out = reference; - - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference"); - -cleanup: - reference_free(reference); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference"); + return 0; } -static int reference_read(gitfo_buf *file_content, time_t *mtime, const char *repo_path, const char *ref_name) +static int reference_read( + git_buf *file_content, + time_t *mtime, + const char *repo_path, + const char *ref_name, + int *updated) { - struct stat st; - char path[GIT_PATH_MAX]; - - /* Determine the full path of the file */ - git__joinpath(path, repo_path, ref_name); + git_buf path = GIT_BUF_INIT; + int result; - if (gitfo_stat(path, &st) < 0 || S_ISDIR(st.st_mode)) - return git__throw(GIT_ENOTFOUND, - "Cannot read reference file '%s'", ref_name); + assert(file_content && repo_path && ref_name); - if (mtime) - *mtime = st.st_mtime; - - if (file_content) - return gitfo_read_file(file_content, path); - - return GIT_SUCCESS; -} - - - - -/***************************************** - * Internal methods - Loose references - *****************************************/ -static int loose_update(git_reference *ref) -{ - int error; - time_t ref_time; - gitfo_buf ref_file = GITFO_BUF_INIT; - - if (ref->type & GIT_REF_PACKED) - return packed_load(ref->owner); - - error = reference_read(NULL, &ref_time, ref->owner->path_repository, ref->name); - if (error < GIT_SUCCESS) - goto cleanup; - - if (ref_time == ref->mtime) - return GIT_SUCCESS; - - error = reference_read(&ref_file, &ref->mtime, ref->owner->path_repository, ref->name); - if (error < GIT_SUCCESS) - goto cleanup; - - if (ref->type == GIT_REF_SYMBOLIC) - error = loose_parse_symbolic(ref, &ref_file); - else if (ref->type == GIT_REF_OID) - error = loose_parse_oid(ref, &ref_file); - else - error = git__throw(GIT_EOBJCORRUPTED, - "Invalid reference type (%d) for loose reference", ref->type); - - gitfo_free_buf(&ref_file); - -cleanup: - if (error != GIT_SUCCESS) { - reference_free(ref); - git_hashtable_remove(ref->owner->references.loose_cache, ref->name); - } + /* Determine the full path of the file */ + if (git_buf_joinpath(&path, repo_path, ref_name) < 0) + return -1; - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to update loose reference"); + result = git_futils_readbuffer_updated(file_content, path.ptr, mtime, updated); + git_buf_free(&path); + return result; } -static int loose_parse_symbolic(git_reference *ref, gitfo_buf *file_content) +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; - reference_symbolic *ref_sym; - refname_start = (const char *)file_content->data; - ref_sym = (reference_symbolic *)ref; + refname_start = (const char *)file_content->ptr; - if (file_content->len < (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 + * before calling this function */ - refname_start += header_len; - free(ref_sym->target); - ref_sym->target = git__strdup(refname_start); - if (ref_sym->target == NULL) - return GIT_ENOMEM; + ref->target.symbolic = git__strdup(refname_start); + GITERR_CHECK_ALLOC(ref->target.symbolic); /* remove newline at the end of file */ - eol = strchr(ref_sym->target, '\n'); + 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_reference *ref, gitfo_buf *file_content) +static int loose_parse_oid(git_oid *oid, git_buf *file_content) { - int error; - reference_oid *ref_oid; char *buffer; - buffer = (char *)file_content->data; - ref_oid = (reference_oid *)ref; + buffer = (char *)file_content->ptr; /* File format: 40 chars (OID) + newline */ - if (file_content->len < 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_mkstr(&ref_oid->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 char *full_path) +static git_ref_t loose_guess_rtype(const git_buf *full_path) { - gitfo_buf ref_file = GITFO_BUF_INIT; - git_rtype type; + git_buf ref_file = GIT_BUF_INIT; + git_ref_t type; type = GIT_REF_INVALID; - if (gitfo_read_file(&ref_file, full_path) == GIT_SUCCESS) { - if (git__prefixcmp((const char *)(ref_file.data), GIT_SYMREF) == 0) + 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 type = GIT_REF_OID; } - gitfo_free_buf(&ref_file); + git_buf_free(&ref_file); return type; } -static int loose_lookup( - git_reference **ref_out, - git_repository *repo, - const char *name, - int skip_symbolic) +static int loose_lookup(git_reference *ref) { - int error = GIT_SUCCESS; - gitfo_buf ref_file = GITFO_BUF_INIT; - git_reference *ref = NULL; - time_t ref_time; + int result, updated; + git_buf ref_file = GIT_BUF_INIT; - *ref_out = NULL; + result = reference_read(&ref_file, &ref->mtime, + ref->owner->path_repository, ref->name, &updated); - error = reference_read(&ref_file, &ref_time, repo->path_repository, name); - if (error < GIT_SUCCESS) - goto cleanup; + if (result < 0) + return result; - if (git__prefixcmp((const char *)(ref_file.data), GIT_SYMREF) == 0) { - if (skip_symbolic) - return GIT_SUCCESS; + if (!updated) + return 0; - error = reference_create(&ref, repo, name, GIT_REF_SYMBOLIC); - if (error < GIT_SUCCESS) - goto cleanup; + if (ref->flags & GIT_REF_SYMBOLIC) { + git__free(ref->target.symbolic); + ref->target.symbolic = NULL; + } + + ref->flags = 0; - error = loose_parse_symbolic(ref, &ref_file); + if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) { + ref->flags |= GIT_REF_SYMBOLIC; + result = loose_parse_symbolic(ref, &ref_file); } else { - error = reference_create(&ref, repo, name, GIT_REF_OID); - if (error < GIT_SUCCESS) - goto cleanup; + ref->flags |= GIT_REF_OID; + result = loose_parse_oid(&ref->target.oid, &ref_file); + } + + git_buf_free(&ref_file); + return result; +} - error = loose_parse_oid(ref, &ref_file); +static int loose_lookup_to_packfile( + struct packref **ref_out, + git_repository *repo, + const char *name) +{ + git_buf ref_file = GIT_BUF_INIT; + struct packref *ref = NULL; + size_t name_len; + + *ref_out = NULL; + + 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; + + if (loose_parse_oid(&ref->oid, &ref_file) < 0) { + git_buf_free(&ref_file); + git__free(ref); + return -1; } - if (error < GIT_SUCCESS) - goto cleanup; + ref->flags = GIT_PACKREF_WAS_LOOSE; - ref->mtime = ref_time; *ref_out = ref; - gitfo_free_buf(&ref_file); - return GIT_SUCCESS; - -cleanup: - gitfo_free_buf(&ref_file); - reference_free(ref); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to lookup loose reference"); + git_buf_free(&ref_file); + return 0; } static int loose_write(git_reference *ref) { - git_filebuf file; - char ref_path[GIT_PATH_MAX]; - int error, contents_size; - char *ref_contents = NULL; + git_filebuf file = GIT_FILEBUF_INIT; + git_buf ref_path = GIT_BUF_INIT; struct stat st; - assert((ref->type & GIT_REF_PACKED) == 0); - - git__joinpath(ref_path, ref->owner->path_repository, ref->name); + if (git_buf_joinpath(&ref_path, ref->owner->path_repository, ref->name) < 0) + return -1; - if ((error = git_filebuf_open(&file, ref_path, GIT_FILEBUF_FORCE)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to write loose reference"); + /* 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 (ref->type & GIT_REF_OID) { - reference_oid *ref_oid = (reference_oid *)ref; + if (git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE) < 0) { + git_buf_free(&ref_path); + return -1; + } - contents_size = GIT_OID_HEXSZ + 1; - ref_contents = git__malloc(contents_size); - if (ref_contents == NULL) { - error = GIT_ENOMEM; - goto unlock; - } + git_buf_free(&ref_path); - git_oid_fmt(ref_contents, &ref_oid->oid); + if (ref->flags & GIT_REF_OID) { + char oid[GIT_OID_HEXSZ + 1]; - } else if (ref->type & GIT_REF_SYMBOLIC) { /* GIT_REF_SYMBOLIC */ - reference_symbolic *ref_sym = (reference_symbolic *)ref; + git_oid_fmt(oid, &ref->target.oid); + oid[GIT_OID_HEXSZ] = '\0'; - contents_size = strlen(GIT_SYMREF) + strlen(ref_sym->target) + 1; - ref_contents = git__malloc(contents_size); - if (ref_contents == NULL) { - error = GIT_ENOMEM; - goto unlock; - } + git_filebuf_printf(&file, "%s\n", oid); - strcpy(ref_contents, GIT_SYMREF); - strcat(ref_contents, ref_sym->target); + } 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 */ } - /* TODO: win32 carriage return when writing references in Windows? */ - ref_contents[contents_size - 1] = '\n'; - - if ((error = git_filebuf_write(&file, ref_contents, contents_size)) < GIT_SUCCESS) - goto unlock; - - error = git_filebuf_commit(&file); - - if (gitfo_stat(ref_path, &st) == GIT_SUCCESS) + if (p_stat(ref_path.ptr, &st) == 0) ref->mtime = st.st_mtime; - free(ref_contents); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write loose reference"); - -unlock: - git_filebuf_cleanup(&file); - free(ref_contents); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write loose reference"); + return git_filebuf_commit(&file, GIT_REFS_FILE_MODE); } - - - - - -/***************************************** - * Internal methods - Packed references - *****************************************/ - static int packed_parse_peel( - reference_oid *tag_ref, - const char **buffer_out, + struct packref *tag_ref, + const char **buffer_out, const char *buffer_end) { const char *buffer = *buffer_out + 1; @@ -436,185 +335,164 @@ 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->ref.name, GIT_REFS_TAGS_DIR) != 0) - return git__throw(GIT_EPACKEDREFSCORRUPTED, "Failed to parse packed reference. Reference is not a tag"); + if (git__prefixcmp(tag_ref->name, GIT_REFS_TAGS_DIR) != 0) + 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_mkstr(&tag_ref->peel_target, 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; - tag_ref->ref.type |= GIT_REF_HAS_PEEL; + return 0; - return GIT_SUCCESS; +corrupt: + giterr_set(GITERR_REFERENCE, "The packed references file is corrupted"); + return -1; } static int packed_parse_oid( - reference_oid **ref_out, - git_repository *repo, + struct packref **ref_out, const char **buffer_out, const char *buffer_end) { - reference_oid *ref = NULL; + struct packref *ref = NULL; const char *buffer = *buffer_out; const char *refname_begin, *refname_end; - int error = GIT_SUCCESS; - int refname_len; - char refname[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH]; + 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_mkstr(&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; - refname_len = refname_end - refname_begin; + if (refname_end[-1] == '\r') + refname_end--; - memcpy(refname, refname_begin, refname_len); - refname[refname_len] = 0; + refname_len = refname_end - refname_begin; - if (refname[refname_len - 1] == '\r') - refname[refname_len - 1] = 0; + ref = git__malloc(sizeof(struct packref) + refname_len + 1); + GITERR_CHECK_ALLOC(ref); - error = reference_create((git_reference **)&ref, repo, refname, GIT_REF_OID); - if (error < GIT_SUCCESS) - goto cleanup; + memcpy(ref->name, refname_begin, refname_len); + ref->name[refname_len] = 0; git_oid_cpy(&ref->oid, &id); - ref->ref.type |= GIT_REF_PACKED; + + ref->flags = 0; *ref_out = ref; *buffer_out = refname_end + 1; - return GIT_SUCCESS; + return 0; -cleanup: - reference_free((git_reference *)ref); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse OID of packed reference"); +corrupt: + git__free(ref); + giterr_set(GITERR_REFERENCE, "The packed references file is corrupted"); + return -1; } static int packed_load(git_repository *repo) { - int error = GIT_SUCCESS; - gitfo_buf packfile = GITFO_BUF_INIT; + int result, updated; + git_buf packfile = GIT_BUF_INIT; const char *buffer_start, *buffer_end; git_refcache *ref_cache = &repo->references; - /* already loaded */ - if (repo->references.packfile != NULL) { - time_t packed_time; - - /* check if we can read the time of the index; - * if we can read it and it matches the time of the - * index we had previously loaded, we don't need to do - * anything else. - * - * if we cannot load the time (e.g. the packfile - * has disappeared) or the time is different, we - * have to reload the packfile */ + /* First we make sure we have allocated the hash table */ + if (ref_cache->packfile == NULL) { + ref_cache->packfile = git_strmap_alloc(); + GITERR_CHECK_ALLOC(ref_cache->packfile); + } - if (!reference_read(NULL, &packed_time, repo->path_repository, GIT_PACKEDREFS_FILE) && - packed_time == ref_cache->packfile_time) - return GIT_SUCCESS; + result = reference_read(&packfile, &ref_cache->packfile_time, + repo->path_repository, GIT_PACKEDREFS_FILE, &updated); - git_hashtable_clear(repo->references.packfile); - } else { - ref_cache->packfile = git_hashtable_alloc( - default_table_size, - reftable_hash, - (git_hash_keyeq_ptr)strcmp); - - if (ref_cache->packfile == NULL) - return GIT_ENOMEM; + /* + * If we couldn't find the file, we need to clear the table and + * return. On any other error, we return that error. If everything + * went fine and the file wasn't updated, then there's nothing new + * for us here, so just return. Anything else means we need to + * refresh the packed refs. + */ + if (result == GIT_ENOTFOUND) { + git_strmap_clear(ref_cache->packfile); + return 0; } - /* read the packfile from disk; - * store its modification time to check for future reloads */ - error = reference_read( - &packfile, - &ref_cache->packfile_time, - repo->path_repository, - GIT_PACKEDREFS_FILE); + if (result < 0) + return -1; - /* there is no packfile on disk; that's ok */ - if (error == GIT_ENOTFOUND) - return GIT_SUCCESS; + if (!updated) + return 0; - if (error < GIT_SUCCESS) - goto cleanup; + /* + * At this point, we want to refresh the packed refs. We already + * have the contents in our buffer. + */ + git_strmap_clear(ref_cache->packfile); - buffer_start = (const char *)packfile.data; - buffer_end = (const char *)(buffer_start) + packfile.len; + 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) { - reference_oid *ref = NULL; + int err; + struct packref *ref = NULL; - error = packed_parse_oid(&ref, repo, &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->ref.name, ref); - if (error < GIT_SUCCESS) { - reference_free((git_reference *)ref); - goto cleanup; - } + git_strmap_insert(ref_cache->packfile, ref->name, ref, err); + if (err < 0) + goto parse_failed; } - gitfo_free_buf(&packfile); - return GIT_SUCCESS; + git_buf_free(&packfile); + return 0; -cleanup: - git_hashtable_free(ref_cache->packfile); +parse_failed: + git_strmap_free(ref_cache->packfile); ref_cache->packfile = NULL; - gitfo_free_buf(&packfile); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to load packed references"); + git_buf_free(&packfile); + return -1; } - - struct dirent_list_data { git_repository *repo; size_t repo_path_len; @@ -624,52 +502,52 @@ struct dirent_list_data { void *callback_payload; }; -static int _dirent_loose_listall(void *_data, char *full_path) +static int _dirent_loose_listall(void *_data, git_buf *full_path) { struct dirent_list_data *data = (struct dirent_list_data *)_data; - char *file_path = full_path + data->repo_path_len; + const char *file_path = full_path->ptr + data->repo_path_len; - if (gitfo_isdir(full_path) == GIT_SUCCESS) - return gitfo_dirent(full_path, GIT_PATH_MAX, _dirent_loose_listall, _data); + 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); } -static int _dirent_loose_load(void *data, char *full_path) +static int _dirent_loose_load(void *data, git_buf *full_path) { git_repository *repository = (git_repository *)data; - git_reference *reference, *old_ref; - char *file_path; - int error; + void *old_ref = NULL; + struct packref *ref; + const char *file_path; + int err; - if (gitfo_isdir(full_path) == GIT_SUCCESS) - return gitfo_dirent(full_path, GIT_PATH_MAX, _dirent_loose_load, repository); + if (git_path_isdir(full_path->ptr) == true) + return git_path_direach(full_path, _dirent_loose_load, repository); - file_path = full_path + strlen(repository->path_repository); - error = loose_lookup(&reference, repository, file_path, 1); - if (error == GIT_SUCCESS && reference != NULL) { - reference->type |= GIT_REF_PACKED; + file_path = full_path->ptr + strlen(repository->path_repository); - if (git_hashtable_insert2(repository->references.packfile, reference->name, reference, (void **)&old_ref) < GIT_SUCCESS) { - reference_free(reference); - return GIT_ENOMEM; - } + if (loose_lookup_to_packfile(&ref, repository, file_path) < 0) + return -1; - if (old_ref != NULL) - reference_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 dirent"); + git__free(old_ref); + return 0; } /* @@ -680,45 +558,37 @@ static int _dirent_loose_load(void *data, char *full_path) */ static int packed_loadloose(git_repository *repository) { - char refs_path[GIT_PATH_MAX]; + git_buf refs_path = GIT_BUF_INIT; + int result; /* the packfile must have been previously loaded! */ assert(repository->references.packfile); - git__joinpath(refs_path, repository->path_repository, GIT_REFS_DIR); - - /* Remove any loose references from the cache */ - { - const void *GIT_UNUSED(_unused); - git_reference *reference; - - GIT_HASHTABLE_FOREACH(repository->references.loose_cache, _unused, reference, - reference_free(reference); - ); - } - - git_hashtable_clear(repository->references.loose_cache); + 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 + * updated loose versions */ - return gitfo_dirent(refs_path, GIT_PATH_MAX, _dirent_loose_load, repository); + result = git_path_direach(&refs_path, _dirent_loose_load, repository); + git_buf_free(&refs_path); + + return result; } /* * Write a single reference into a packfile */ -static int packed_write_ref(reference_oid *ref, git_filebuf *file) +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); oid[GIT_OID_HEXSZ] = 0; - /* + /* * For references that peel to an object in the repo, we must * write the resulting peel on a separate line, e.g. * @@ -728,48 +598,48 @@ static int packed_write_ref(reference_oid *ref, git_filebuf *file) * This obviously only applies to tags. * The required peels have already been loaded into `ref->peel_target`. */ - if (ref->ref.type & GIT_REF_HAS_PEEL) { + if (ref->flags & GIT_PACKREF_HAS_PEEL) { char peel[GIT_OID_HEXSZ + 1]; - git_oid_fmt(peel, &ref->peel_target); + git_oid_fmt(peel, &ref->peel); peel[GIT_OID_HEXSZ] = 0; - error = git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->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->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; } /* * Find out what object this reference resolves to. * - * For references that point to a 'big' tag (e.g. an + * For references that point to a 'big' tag (e.g. an * actual tag object on the repository), we need to * cache on the packfile the OID of the object to * which that 'big tag' is pointing to. */ -static int packed_find_peel(reference_oid *ref) +static int packed_find_peel(git_repository *repo, struct packref *ref) { git_object *object; - int error; - if (ref->ref.type & GIT_REF_HAS_PEEL) - return GIT_SUCCESS; + if (ref->flags & GIT_PACKREF_HAS_PEEL) + return 0; /* * Only applies to tags, i.e. references * in the /refs/tags folder */ - if (git__prefixcmp(ref->ref.name, GIT_REFS_TAGS_DIR) != 0) - return GIT_SUCCESS; + if (git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) != 0) + return 0; /* * Find the tagged object in the repository */ - error = git_object_lookup(&object, ref->ref.owner, &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; @@ -782,8 +652,8 @@ static int packed_find_peel(reference_oid *ref) /* * Find the object pointed at by this tag */ - git_oid_cpy(&ref->peel_target, git_tag_target_oid(tag)); - ref->ref.type |= GIT_REF_HAS_PEEL; + git_oid_cpy(&ref->peel, git_tag_target_oid(tag)); + ref->flags |= GIT_PACKREF_HAS_PEEL; /* * The reference has now cached the resolved OID, and is @@ -792,9 +662,8 @@ static int packed_find_peel(reference_oid *ref) */ } - git_object_close(object); - - return GIT_SUCCESS; + git_object_free(object); + return 0; } /* @@ -811,44 +680,45 @@ static int packed_find_peel(reference_oid *ref) static int packed_remove_loose(git_repository *repo, git_vector *packing_list) { unsigned int i; - char full_path[GIT_PATH_MAX]; - int error = GIT_SUCCESS; - git_reference *reference; + git_buf full_path = GIT_BUF_INIT; + int failed = 0; for (i = 0; i < packing_list->length; ++i) { - git_reference *ref = git_vector_get(packing_list, i); + struct packref *ref = git_vector_get(packing_list, i); - /* Ensure the packed reference doesn't exist - * in a (more up-to-date?) state as a loose reference - */ - reference = git_hashtable_lookup(ref->owner->references.loose_cache, ref->name); - if (reference != NULL) + if ((ref->flags & GIT_PACKREF_WAS_LOOSE) == 0) continue; - git__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 (gitfo_exists(full_path) == GIT_SUCCESS && - gitfo_unlink(full_path) < GIT_SUCCESS) - error = GIT_EOSERR; + if (git_path_exists(full_path.ptr) == true && p_unlink(full_path.ptr) < 0) { + if (failed) + continue; + + 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, * but we should keep going and remove as many as possible. * After we've removed as many files as possible, we return * the error code anyway. - * - * TODO: mark this with a very special error code? - * GIT_EFAILTORMLOOSE */ } - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to remove loose packed reference"); + git_buf_free(&full_path); + return failed ? -1 : 0; } static int packed_sort(const void *a, const void *b) { - const git_reference *ref_a = *(const git_reference **)a; - const git_reference *ref_b = *(const git_reference **)b; + const struct packref *ref_a = (const struct packref *)a; + const struct packref *ref_b = (const struct packref *)b; return strcmp(ref_a->name, ref_b->name); } @@ -858,408 +728,450 @@ static int packed_sort(const void *a, const void *b) */ static int packed_write(git_repository *repo) { - git_filebuf pack_file; - int error; + git_filebuf pack_file = GIT_FILEBUF_INIT; unsigned int i; - char pack_file_path[GIT_PATH_MAX]; - + 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 write packed reference"); + 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 */ { - git_reference *reference; - const void *GIT_UNUSED(_unused); + struct packref *reference; - GIT_HASHTABLE_FOREACH(repo->references.packfile, _unused, reference, - git_vector_insert(&packing_list, 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! */ - git__joinpath(pack_file_path, repo->path_repository, GIT_PACKEDREFS_FILE); - if ((error = git_filebuf_open(&pack_file, pack_file_path, 0)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to write packed reference"); + if (git_buf_joinpath(&pack_file_path, repo->path_repository, GIT_PACKEDREFS_FILE) < 0) + goto cleanup_memory; + + 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) - return git__rethrow(error, "Failed to write packed reference"); + if (git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER) < 0) + goto cleanup_packfile; for (i = 0; i < packing_list.length; ++i) { - reference_oid *ref = (reference_oid *)git_vector_get(&packing_list, i); - - /* only direct references go to the packfile; otherwise - * this is a disaster */ - assert(ref->ref.type & GIT_REF_OID); + struct packref *ref = (struct packref *)git_vector_get(&packing_list, i); - if ((error = packed_find_peel(ref)) < GIT_SUCCESS) - 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); - - /* 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; + if (git_filebuf_commit(&pack_file, GIT_PACKEDREFS_FILE_MODE) < 0) + goto cleanup_memory; - error = packed_remove_loose(repo, &packing_list); + /* 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; - if (gitfo_stat(pack_file_path, &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); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to write packed reference"); -} + /* we're good now */ + return 0; -/***************************************** - * Internal methods - reference creation - *****************************************/ +cleanup_packfile: + git_filebuf_cleanup(&pack_file); -static int reference_create_symbolic(git_reference **ref_out, git_repository *repo, const char *name, const char *target, int force) -{ - char normalized[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH]; - int error = GIT_SUCCESS, updated = 0; - git_reference *ref = NULL, *old_ref = NULL; +cleanup_memory: + git_vector_free(&packing_list); + git_buf_free(&pack_file_path); - if (git_reference_lookup(&ref, repo, name) == GIT_SUCCESS && !force) - return git__throw(GIT_EEXISTS, "Failed to create symbolic reference. Reference already exists"); + return -1; +} - /* - * If they old ref was of the same type, then we can just update - * it (once we've checked that the target is valid). Otherwise we - * need a new reference because we can't make a symbolic ref out - * of an oid one. - * If if didn't exist, then we need to create a new one anyway. - */ - if (ref && ref->type & GIT_REF_SYMBOLIC){ - updated = 1; - } else { - ref = NULL; - error = reference_create(&ref, repo, name, GIT_REF_SYMBOLIC); - if (error < GIT_SUCCESS) - goto cleanup; - } +struct reference_available_t { + const char *new_ref; + const char *old_ref; + int available; +}; - /* The target can aither be the name of an object id reference or the name of another symbolic reference */ - error = normalize_name(normalized, target, 0); - if (error < GIT_SUCCESS) - goto cleanup; +static int _reference_available_cb(const char *ref, void *data) +{ + struct reference_available_t *d; - /* set the target; this will write the reference on disk */ - error = git_reference_set_target(ref, normalized); - if (error < GIT_SUCCESS) - goto cleanup; + assert(ref && data); + d = (struct reference_available_t *)data; - /* - * If we didn't update the ref, then we need to insert or replace - * it in the loose cache. If we replaced a ref, free it. - */ - if (!updated){ - error = git_hashtable_insert2(repo->references.loose_cache, ref->name, ref, (void **) &old_ref); - if (error < GIT_SUCCESS) - goto cleanup; + 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_ref) - reference_free(old_ref); + if (!strncmp(d->new_ref, ref, cmplen) && lead[cmplen] == '/') { + d->available = 0; + return -1; + } } - *ref_out = ref; - - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create symbolic reference"); - -cleanup: - reference_free(ref); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create symbolic reference"); + return 0; } -static int reference_create_oid(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id, int force) +static int reference_path_available( + git_repository *repo, + const char *ref, + const char* old_ref) { - int error = GIT_SUCCESS, updated = 0; - git_reference *ref = NULL, *old_ref = NULL; + struct reference_available_t data; - if(git_reference_lookup(&ref, repo, name) == GIT_SUCCESS && !force) - return git__throw(GIT_EEXISTS, "Failed to create reference OID. Reference already exists"); + data.new_ref = ref; + data.old_ref = old_ref; + data.available = 1; - /* - * If they old ref was of the same type, then we can just update - * it (once we've checked that the target is valid). Otherwise we - * need a new reference because we can't make a symbolic ref out - * of an oid one. - * If if didn't exist, then we need to create a new one anyway. - */ - if (ref && ref-> type & GIT_REF_OID){ - updated = 1; - } else { - ref = NULL; - error = reference_create(&ref, repo, name, GIT_REF_OID); - if (error < GIT_SUCCESS) - goto cleanup; + if (git_reference_foreach(repo, GIT_REF_LISTALL, + _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; } - /* set the oid; this will write the reference on disk */ - error = git_reference_set_oid(ref, id); - if (error < GIT_SUCCESS) - goto cleanup; + return 0; +} - if(!updated){ - error = git_hashtable_insert2(repo->references.loose_cache, ref->name, ref, (void **) &old_ref); - if (error < GIT_SUCCESS) - goto cleanup; +static int reference_exists(int *exists, git_repository *repo, const char *ref_name) +{ + git_buf ref_path = GIT_BUF_INIT; - if(old_ref) - reference_free(old_ref); - } + if (packed_load(repo) < 0) + return -1; - *ref_out = ref; + if (git_buf_joinpath(&ref_path, repo->path_repository, ref_name) < 0) + return -1; - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference OID"); + if (git_path_isfile(ref_path.ptr) == true || + git_strmap_exists(repo->references.packfile, ref_path.ptr)) + { + *exists = 1; + } else { + *exists = 0; + } -cleanup: - reference_free(ref); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference OID"); + git_buf_free(&ref_path); + return 0; } /* - * Rename a reference - * - * If the reference is packed, we need to rewrite the - * packfile to remove the reference from it and create - * the reference back as a loose one. + * Check if a reference could be written to disk, based on: * - * If the reference is loose, we just rename it on - * the filesystem. + * - Whether a reference with the same name already exists, + * and we are allowing or disallowing overwrites * - * We also need to re-insert the reference on its corresponding - * in-memory cache, since the caches are indexed by refname. + * - Whether the name of the reference would collide with + * an existing path */ -static int reference_rename(git_reference *ref, const char *new_name, int force) +static int reference_can_write( + git_repository *repo, + const char *refname, + const char *previous_name, + int force) { - int error; - char *old_name; - char old_path[GIT_PATH_MAX], new_path[GIT_PATH_MAX], normalized_name[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH]; - git_reference *looked_up_ref, *old_ref = NULL; + /* 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; + } + } + + /* 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; +} - assert(ref); - /* Ensure the name is valid */ - error = normalize_name(normalized_name, new_name, ref->type & GIT_REF_OID); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to rename reference"); +static int packed_lookup(git_reference *ref) +{ + struct packref *pack_ref = NULL; + git_strmap *packfile_refs; + khiter_t pos; + + if (packed_load(ref->owner) < 0) + return -1; + + /* 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 0; + + if (ref->flags & GIT_REF_SYMBOLIC) { + git__free(ref->target.symbolic); + ref->target.symbolic = NULL; + } - /* Ensure we're not going to overwrite an existing reference - unless the user has allowed us */ - error = git_reference_lookup(&looked_up_ref, ref->owner, new_name); - if (error == GIT_SUCCESS && !force) - return git__throw(GIT_EEXISTS, "Failed to rename reference. Reference already exists"); + /* Look up on the 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; + } - if (error < GIT_SUCCESS && - error != GIT_ENOTFOUND) - return git__rethrow(error, "Failed to rename reference"); + 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); - old_name = ref->name; - ref->name = git__strdup(new_name); + return 0; +} - if (ref->name == NULL) { - ref->name = old_name; - return GIT_ENOMEM; +static int reference_lookup(git_reference *ref) +{ + int result; + + result = loose_lookup(ref); + if (result == 0) + return 0; + + /* 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; } - if (ref->type & GIT_REF_PACKED) { - /* write the packfile to disk; note - * that the state of the in-memory cache is not - * consistent, because the reference is indexed - * by its old name but it already has the new one. - * This doesn't affect writing, though, and allows - * us to rollback if writing fails - */ + /* unexpected error; free the reference */ + git_reference_free(ref); + return result; +} - ref->type &= ~GIT_REF_PACKED; +/* + * Delete a reference. + * This is an internal method; the reference is removed + * from disk or the packfile, but the pointer is not freed + */ +static int reference_delete(git_reference *ref) +{ + int result; - /* Create the loose ref under its new name */ - error = loose_write(ref); - if (error < GIT_SUCCESS) { - ref->type |= GIT_REF_PACKED; - goto cleanup; + assert(ref); + + /* If the reference is packed, this is an expensive operation. + * 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 (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; } - /* Remove from the packfile cache in order to avoid packing it back - * Note : we do not rely on git_reference_delete() because this would - * invalidate the reference. - */ - git_hashtable_remove(ref->owner->references.packfile, old_name); + packref = git_strmap_value_at(packfile_refs, pos); + git_strmap_delete_at(packfile_refs, pos); - /* Recreate the packed-refs file without the reference */ - error = packed_write(ref->owner); - if (error < GIT_SUCCESS) - goto rename_loose_to_old_name; + 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 */ } else { - git__joinpath(old_path, ref->owner->path_repository, old_name); - git__joinpath(new_path, ref->owner->path_repository, ref->name); + git_reference *ref_in_pack; + git_buf full_path = GIT_BUF_INIT; - error = gitfo_mv_force(old_path, new_path); - if (error < GIT_SUCCESS) - goto cleanup; + if (git_buf_joinpath(&full_path, ref->owner->path_repository, ref->name) < 0) + return -1; - /* Once succesfully renamed, remove from the cache the reference known by its old name*/ - git_hashtable_remove(ref->owner->references.loose_cache, old_name); - } + result = p_unlink(full_path.ptr); + git_buf_free(&full_path); /* done with path at this point */ - /* Store the renamed reference into the loose ref cache */ - error = git_hashtable_insert2(ref->owner->references.loose_cache, ref->name, ref, (void **) &old_ref); + if (result < 0) { + giterr_set(GITERR_OS, "Failed to unlink '%s'", full_path.ptr); + return -1; + } - /* If we force-replaced, we need to free the old reference */ - if(old_ref) - reference_free(old_ref); + /* 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) == 0) { + assert((ref_in_pack->flags & GIT_REF_PACKED) != 0); + return git_reference_delete(ref_in_pack); + } - free(old_name); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to rename reference"); + giterr_clear(); + } -cleanup: - /* restore the old name if this failed */ - free(ref->name); - ref->name = old_name; - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to rename reference"); - -rename_loose_to_old_name: - /* If we hit this point. Something *bad* happened! Think "Ghostbusters - * crossing the streams" definition of bad. - * Either the packed-refs has been correctly generated and something else - * has gone wrong, or the writing of the new packed-refs has failed, and - * we're stuck with the old one. As a loose ref always takes priority over - * a packed ref, we'll eventually try and rename the generated loose ref to - * its former name. It even that fails, well... we might have lost the reference - * for good. :-/ - */ - - git__joinpath(old_path, ref->owner->path_repository, ref->name); - git__joinpath(new_path, ref->owner->path_repository, old_name); - - /* No error checking. We'll return the initial error */ - gitfo_mv_force(old_path, new_path); - - /* restore the old name */ - free(ref->name); - ref->name = old_name; - - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to rename reference"); + return 0; } -/***************************************** - * External Library API - *****************************************/ +int git_reference_delete(git_reference *ref) +{ + int result = reference_delete(ref); + git_reference_free(ref); + return result; +} -/** - * Constructors - */ -int git_reference_lookup(git_reference **ref_out, git_repository *repo, const char *name) +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_PATH_MAX]; + 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, 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; - /* First, check has been previously loaded and cached */ - *ref_out = git_hashtable_lookup(repo->references.loose_cache, normalized_name); - if (*ref_out != NULL) - return loose_update(*ref_out); + scan = git__calloc(1, sizeof(git_reference)); + GITERR_CHECK_ALLOC(scan); - /* Then check if there is a loose file for that reference.*/ - error = loose_lookup(ref_out, repo, normalized_name, 0); + scan->name = git__calloc(GIT_REFNAME_MAX + 1, sizeof(char)); + GITERR_CHECK_ALLOC(scan->name); - /* If the file exists, we store it on the cache */ - if (error == GIT_SUCCESS) - return git_hashtable_insert(repo->references.loose_cache, (*ref_out)->name, (*ref_out)); + if ((result = normalize_name(scan->name, GIT_REFNAME_MAX, name, 0)) < 0) { + git_reference_free(scan); + return result; + } - /* The loose lookup has failed, but not because the reference wasn't found; - * probably the loose reference is corrupted. this is bad. */ - if (error != GIT_ENOTFOUND) - return git__rethrow(error, "Failed to lookup reference"); + scan->target.symbolic = git__strdup(scan->name); + GITERR_CHECK_ALLOC(scan->target.symbolic); - /* - * If we cannot find a loose reference, we look into the packfile - * Load the packfile first if it hasn't been loaded - */ - /* load all the packed references */ - error = packed_load(repo); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to lookup reference"); - - /* Look up on the packfile */ - *ref_out = git_hashtable_lookup(repo->references.packfile, normalized_name); - if (*ref_out != NULL) - return GIT_SUCCESS; + scan->owner = repo; + scan->flags = GIT_REF_SYMBOLIC; - /* The reference doesn't exist anywhere */ - return git__throw(GIT_ENOTFOUND, "Failed to lookup reference. Reference doesn't exist"); -} + 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); -int git_reference_create_symbolic(git_reference **ref_out, git_repository *repo, const char *name, const char *target) -{ - return reference_create_symbolic(ref_out, repo, name, target, 0); -} + scan->mtime = 0; -int git_reference_create_symbolic_f(git_reference **ref_out, git_repository *repo, const char *name, const char *target) -{ - return reference_create_symbolic(ref_out, repo, name, target, 1); -} + if ((result = reference_lookup(scan)) < 0) + return result; /* lookup git_reference_free on scan already */ + } -int git_reference_create_oid(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id) -{ - return reference_create_oid(ref_out, repo, name, id, 0); -} + 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; + } -int git_reference_create_oid_f(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id) -{ - return reference_create_oid(ref_out, repo, name, id, 1); + *ref_out = scan; + return 0; } /** * Getters */ -git_rtype git_reference_type(git_reference *ref) +git_ref_t git_reference_type(git_reference *ref) { assert(ref); - if (ref->type & GIT_REF_OID) + if (ref->flags & GIT_REF_OID) return GIT_REF_OID; - if (ref->type & GIT_REF_SYMBOLIC) + if (ref->flags & GIT_REF_SYMBOLIC) return GIT_REF_SYMBOLIC; return GIT_REF_INVALID; } +int git_reference_is_packed(git_reference *ref) +{ + assert(ref); + return !!(ref->flags & GIT_REF_PACKED); +} + const char *git_reference_name(git_reference *ref) { assert(ref); @@ -1276,110 +1188,129 @@ const git_oid *git_reference_oid(git_reference *ref) { assert(ref); - if ((ref->type & GIT_REF_OID) == 0) + if ((ref->flags & GIT_REF_OID) == 0) return NULL; - if (loose_update(ref) < GIT_SUCCESS) - return NULL; - - return &((reference_oid *)ref)->oid; + return &ref->target.oid; } const char *git_reference_target(git_reference *ref) { assert(ref); - if ((ref->type & GIT_REF_SYMBOLIC) == 0) + if ((ref->flags & GIT_REF_SYMBOLIC) == 0) return NULL; - if (loose_update(ref) < GIT_SUCCESS) - return NULL; + return ref->target.symbolic; +} + +int git_reference_create_symbolic( + git_reference **ref_out, + git_repository *repo, + const char *name, + const char *target, + int force) +{ + char normalized[GIT_REFNAME_MAX]; + git_reference *ref = NULL; + + if (normalize_name(normalized, sizeof(normalized), name, 0) < 0) + return -1; + + if (reference_can_write(repo, normalized, NULL, force) < 0) + return -1; + + if (reference_alloc(&ref, repo, normalized) < 0) + return -1; + + ref->flags |= GIT_REF_SYMBOLIC; - return ((reference_symbolic *)ref)->target; + /* set the target; this will normalize the name automatically + * and write the reference on disk */ + 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 0; } -/** - * Setters - */ +int git_reference_create_oid( + git_reference **ref_out, + git_repository *repo, + const char *name, + const git_oid *id, + int force) +{ + git_reference *ref = NULL; + char normalized[GIT_REFNAME_MAX]; + + if (normalize_name(normalized, sizeof(normalized), name, 1) < 0) + return -1; + + if (reference_can_write(repo, normalized, NULL, force) < 0) + return -1; + if (reference_alloc(&ref, repo, name) < 0) + return -1; + + ref->flags |= GIT_REF_OID; + + /* set the oid; this will write the reference on disk */ + if (git_reference_set_oid(ref, id) < 0) { + git_reference_free(ref); + return -1; + } + + if (ref_out == NULL) { + git_reference_free(ref); + } else { + *ref_out = ref; + } + + return 0; +} /* * Change the OID target of a reference. * - * For loose references, just change the oid in memory - * and overwrite the file in disk. + * For both loose and packed references, just change + * the oid in memory and (over)write the file in disk. * - * For packed files, this is not pretty: - * For performance reasons, we write the new reference - * loose on disk (it replaces the old on the packfile), - * but we cannot invalidate the pointer to the reference, - * and most importantly, the `packfile` object must stay - * consistent with the representation of the packfile - * on disk. This is what we need to: - * - * 1. Copy the reference - * 2. Change the oid on the original - * 3. Write the original to disk - * 4. Write the original to the loose cache - * 5. Replace the original with the copy (old reference) in the packfile cache + * We do not repack packed references because of performance + * reasons. */ int git_reference_set_oid(git_reference *ref, const git_oid *id) { - reference_oid *ref_oid; - reference_oid *ref_old = NULL; - int error = GIT_SUCCESS; - - if ((ref->type & GIT_REF_OID) == 0) - return git__throw(GIT_EINVALIDREFSTATE, "Failed to set OID target of reference. Not an OID reference"); + git_odb *odb = NULL; - ref_oid = (reference_oid *)ref; + if ((ref->flags & GIT_REF_OID) == 0) { + giterr_set(GITERR_REFERENCE, "Cannot set OID on symbolic reference"); + return -1; + } assert(ref->owner); + 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 (!git_odb_exists(git_repository_database(ref->owner), id)) - return git__throw(GIT_ENOTFOUND, "Failed to set OID target of reference. OID doesn't exist in ODB"); - - /* duplicate the reference; - * this copy will stay on the packfile cache */ - if (ref->type & GIT_REF_PACKED) { - ref_old = git__malloc(sizeof(reference_oid)); - if (ref_old == NULL) - return GIT_ENOMEM; - - ref_old->ref.name = git__strdup(ref->name); - if (ref_old->ref.name == NULL) { - free(ref_old); - return GIT_ENOMEM; - } - } - - git_oid_cpy(&ref_oid->oid, id); - ref->type &= ~GIT_REF_HAS_PEEL; - - error = loose_write(ref); - if (error < GIT_SUCCESS) - goto cleanup; - - if (ref->type & GIT_REF_PACKED) { - /* insert the original on the loose cache */ - error = git_hashtable_insert(ref->owner->references.loose_cache, ref->name, ref); - if (error < GIT_SUCCESS) - goto cleanup; - - ref->type &= ~GIT_REF_PACKED; - - /* replace the original in the packfile with the copy */ - error = git_hashtable_insert(ref->owner->references.packfile, ref_old->ref.name, ref_old); - if (error < GIT_SUCCESS) - goto cleanup; + if (!git_odb_exists(odb, id)) { + giterr_set(GITERR_REFERENCE, + "Target OID for the reference doesn't exist on the repository"); + return -1; } - return GIT_SUCCESS; + /* Update the OID value on `ref` */ + git_oid_cpy(&ref->target.oid, id); -cleanup: - reference_free((git_reference *)ref_old); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to set OID target of reference"); + /* Write back to disk */ + return loose_write(ref); } /* @@ -1391,149 +1322,177 @@ cleanup: */ int git_reference_set_target(git_reference *ref, const char *target) { - reference_symbolic *ref_sym; + char normalized[GIT_REFNAME_MAX]; - if ((ref->type & 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; + } - ref_sym = (reference_symbolic *)ref; + if (normalize_name(normalized, sizeof(normalized), target, 0)) + return -1; - free(ref_sym->target); - ref_sym->target = git__strdup(target); - if (ref_sym->target == NULL) - return GIT_ENOMEM; + git__free(ref->target.symbolic); + ref->target.symbolic = git__strdup(normalized); + GITERR_CHECK_ALLOC(ref->target.symbolic); return loose_write(ref); } -/** - * Other - */ - -/* - * Delete a reference. - * - * If the reference is packed, this is an expensive - * operation. We need to remove the reference from - * the memory cache and then rewrite the whole pack - * - * If the reference is loose, we remove it on - * the filesystem and update the in-memory cache - * accordingly. We also make sure that an older version - * of it doesn't exist as a packed reference. If this - * is the case, this packed reference is removed as well. - * - * This obviously invalidates the `ref` pointer. - */ -int git_reference_delete(git_reference *ref) +int git_reference_rename(git_reference *ref, const char *new_name, int force) { - int error; - git_reference *reference; + int result; + git_buf aux_path = GIT_BUF_INIT; + char normalized[GIT_REFNAME_MAX]; - assert(ref); + const char *head_target = NULL; + git_reference *head = NULL; - if (ref->type & GIT_REF_PACKED) { - /* load the existing packfile */ - if ((error = packed_load(ref->owner)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to delete reference"); - - git_hashtable_remove(ref->owner->references.packfile, ref->name); - error = packed_write(ref->owner); + if (normalize_name(normalized, sizeof(normalized), + new_name, ref->flags & GIT_REF_OID) < 0) + return -1; + + 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. */ + 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 + * named `new_name`. Note that using the internal `reference_delete` + * 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 (reference_delete(ref) < 0) + goto cleanup; + + /* + * Finally we can create the new reference. + */ + if (ref->flags & GIT_REF_SYMBOLIC) { + result = git_reference_create_symbolic( + NULL, ref->owner, new_name, ref->target.symbolic, force); } else { - char full_path[GIT_PATH_MAX]; - git__joinpath(full_path, ref->owner->path_repository, ref->name); - git_hashtable_remove(ref->owner->references.loose_cache, ref->name); - error = gitfo_unlink(full_path); - if (error < GIT_SUCCESS) - goto cleanup; + result = git_reference_create_oid( + NULL, ref->owner, new_name, &ref->target.oid, force); + } - /* When deleting a loose reference, we have to ensure that an older - * packed version of it doesn't exist - */ - if (!git_reference_lookup(&reference, ref->owner, ref->name)) { - assert((reference->type & GIT_REF_PACKED) != 0); - error = git_reference_delete(reference); - } + if (result < 0) + goto rollback; + + /* + * Check if we have to update HEAD. + */ + if (git_reference_lookup(&head, ref->owner, GIT_HEAD_FILE) < 0) { + giterr_set(GITERR_REFERENCE, + "Failed to update HEAD after renaming reference"); + goto cleanup; } -cleanup: - reference_free(ref); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to delete reference"); -} + head_target = git_reference_target(head); -int git_reference_rename(git_reference *ref, const char *new_name) -{ - return reference_rename(ref, new_name, 0); -} + if (head_target && !strcmp(head_target, ref->name)) { + 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; + } + } -int git_reference_rename_f(git_reference *ref, const char *new_name) -{ - return reference_rename(ref, new_name, 1); -} + /* + * Rename the reflog file. + */ + if (git_buf_join_n(&aux_path, '/', 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name) < 0) + goto cleanup; -int git_reference_resolve(git_reference **resolved_ref, git_reference *ref) -{ - git_repository *repo; - int error, i; + if (git_path_exists(aux_path.ptr) == true) { + if (git_reflog_rename(ref, new_name) < 0) + goto cleanup; + } else { + giterr_clear(); + } - assert(resolved_ref && ref); - *resolved_ref = NULL; + /* + * Change the name of the reference given by the user. + */ + git__free(ref->name); + ref->name = git__strdup(new_name); - if ((error = loose_update(ref)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to resolve reference"); - - repo = ref->owner; + /* The reference is no longer packed */ + ref->flags &= ~GIT_REF_PACKED; - for (i = 0; i < MAX_NESTING_LEVEL; ++i) { - reference_symbolic *ref_sym; + git_reference_free(head); + git_buf_free(&aux_path); + return 0; - *resolved_ref = ref; +cleanup: + git_reference_free(head); + git_buf_free(&aux_path); + return -1; - if (ref->type & GIT_REF_OID) - return GIT_SUCCESS; +rollback: + /* + * Try to create the old reference again, ignore failures + */ + if (ref->flags & GIT_REF_SYMBOLIC) + git_reference_create_symbolic( + NULL, ref->owner, ref->name, ref->target.symbolic, 0); + else + git_reference_create_oid( + NULL, ref->owner, ref->name, &ref->target.oid, 0); - ref_sym = (reference_symbolic *)ref; - if ((error = git_reference_lookup(&ref, repo, ref_sym->target)) < GIT_SUCCESS) - return error; - } + /* The reference is no longer packed */ + ref->flags &= ~GIT_REF_PACKED; - return git__throw(GIT_ENOMEM, "Failed to resolve reference. Reference is too nested"); + git_buf_free(&aux_path); + return -1; } -int git_reference_packall(git_repository *repo) +int git_reference_resolve(git_reference **ref_out, git_reference *ref) { - int error; - - /* load the existing packfile */ - if ((error = packed_load(repo)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to pack references"); + if (ref->flags & GIT_REF_OID) + return git_reference_lookup(ref_out, ref->owner, ref->name); + else + return git_reference_lookup_resolved(ref_out, ref->owner, ref->target.symbolic, -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"); +int git_reference_packall(git_repository *repo) +{ + 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; - /* write it back to disk */ - return packed_write(repo); + return 0; } -int git_reference_listcb(git_repository *repo, unsigned int list_flags, int (*callback)(const char *, void *), void *payload) +int git_reference_foreach( + git_repository *repo, + unsigned int list_flags, + int (*callback)(const char *, void *), + void *payload) { - int error; + int result; struct dirent_list_data data; - char refs_path[GIT_PATH_MAX]; + git_buf refs_path = GIT_BUF_INIT; /* list all the packed references first */ if (list_flags & GIT_REF_PACKED) { const char *ref_name; - void *GIT_UNUSED(_unused); + 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(repo->references.packfile, ref_name, _unused, - 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 @@ -1545,19 +1504,25 @@ int git_reference_listcb(git_repository *repo, unsigned int list_flags, int (*ca data.callback = callback; data.callback_payload = payload; + 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); - git__joinpath(refs_path, repo->path_repository, GIT_REFS_DIR); - return gitfo_dirent(refs_path, GIT_PATH_MAX, _dirent_loose_listall, &data); + return result; } -int cb__reflist_add(const char *ref, void *data) +static int cb__reflist_add(const char *ref, void *data) { return git_vector_insert((git_vector *)data, git__strdup(ref)); } -int git_reference_listall(git_strarray *array, git_repository *repo, unsigned int list_flags) +int git_reference_list( + git_strarray *array, + git_repository *repo, + unsigned int list_flags) { - int error; git_vector ref_list; assert(array && repo); @@ -1565,73 +1530,44 @@ int git_reference_listall(git_strarray *array, git_repository *repo, unsigned in array->strings = NULL; array->count = 0; - if (git_vector_init(&ref_list, 8, NULL) < GIT_SUCCESS) - return GIT_ENOMEM; - - error = git_reference_listcb(repo, list_flags, &cb__reflist_add, (void *)&ref_list); + if (git_vector_init(&ref_list, 8, NULL) < 0) + return -1; - 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; } - - - -/***************************************** - * Init/free (repository API) - *****************************************/ -int git_repository__refcache_init(git_refcache *refs) +int git_reference_reload(git_reference *ref) { - assert(refs); - - refs->loose_cache = git_hashtable_alloc( - default_table_size, - reftable_hash, - (git_hash_keyeq_ptr)strcmp); - - /* packfile loaded lazily */ - refs->packfile = NULL; - - return (refs->loose_cache) ? GIT_SUCCESS : GIT_ENOMEM; + return reference_lookup(ref); } void git_repository__refcache_free(git_refcache *refs) { - git_reference *reference; - const void *GIT_UNUSED(_unused); - assert(refs); - GIT_HASHTABLE_FOREACH(refs->loose_cache, _unused, reference, - reference_free(reference); - ); - - git_hashtable_free(refs->loose_cache); - if (refs->packfile) { - GIT_HASHTABLE_FOREACH(refs->packfile, _unused, reference, - reference_free(reference); - ); + struct packref *reference; + + git_strmap_foreach_value(refs->packfile, reference, { + git__free(reference); + }); - git_hashtable_free(refs->packfile); + git_strmap_free(refs->packfile); } } - - -/***************************************** - * Name normalization - *****************************************/ -static int check_valid_ref_char(char ch) +static int is_valid_ref_char(char ch) { - if (ch <= ' ') - return GIT_ERROR; + if ((unsigned) ch <= ' ') + return 0; switch (ch) { case '~': @@ -1641,48 +1577,53 @@ static int check_valid_ref_char(char ch) case '?': case '[': case '*': - return GIT_ERROR; - break; - + return 0; default: - return GIT_SUCCESS; + return 1; } } -static int normalize_name(char *buffer_out, const char *name, int is_oid_ref) +static int normalize_name( + char *buffer_out, + size_t out_size, + const char *name, + int is_oid_ref) { const char *name_end, *buffer_out_start; - char *current; + const char *current; int contains_a_slash = 0; assert(name && buffer_out); buffer_out_start = buffer_out; - current = (char *)name; + current = name; name_end = name + strlen(name); + /* Terminating null byte */ + out_size--; + /* 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) { - if (check_valid_ref_char(*current)) - return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name contains invalid characters"); + while (current < name_end && out_size) { + if (!is_valid_ref_char(*current)) + 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 == '/') { @@ -1695,17 +1636,25 @@ static int normalize_name(char *buffer_out, const char *name, int is_oid_ref) contains_a_slash = 1; *buffer_out++ = *current++; + out_size--; } + if (!out_size) + 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 * middle of a merge */ - if (is_oid_ref && !contains_a_slash && (strcmp(name, GIT_HEAD_FILE) && strcmp(name, GIT_MERGE_HEAD_FILE))) - return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name contains no slashes"); + if (is_oid_ref && + !contains_a_slash && + strcmp(name, GIT_HEAD_FILE) != 0 && + strcmp(name, GIT_MERGE_HEAD_FILE) != 0 && + strcmp(name, GIT_FETCH_HEAD_FILE) != 0) + 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'; @@ -1715,19 +1664,44 @@ static int normalize_name(char *buffer_out, const char *name, int is_oid_ref) */ 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(char *buffer_out, const char *name) +int git_reference__normalize_name( + char *buffer_out, + size_t out_size, + const char *name) { - return normalize_name(buffer_out, name, 0); + return normalize_name(buffer_out, out_size, name, 0); } -int git_reference__normalize_name_oid(char *buffer_out, const char *name) +int git_reference__normalize_name_oid( + char *buffer_out, + size_t out_size, + const char *name) { - return normalize_name(buffer_out, name, 1); + 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 b8f3e2f6d..369e91e1c 100644 --- a/src/refs.h +++ b/src/refs.h @@ -1,43 +1,81 @@ +/* + * 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_refs_h__ #define INCLUDE_refs_h__ #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/" #define GIT_REFS_TAGS_DIR GIT_REFS_DIR "tags/" #define GIT_REFS_REMOTES_DIR GIT_REFS_DIR "remotes/" +#define GIT_REFS_DIR_MODE 0777 +#define GIT_REFS_FILE_MODE 0666 + +#define GIT_RENAMED_REF_FILE GIT_REFS_DIR "RENAMED-REF" #define GIT_SYMREF "ref: " #define GIT_PACKEDREFS_FILE "packed-refs" #define GIT_PACKEDREFS_HEADER "# pack-refs with: peeled " -#define MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH 100 +#define GIT_PACKEDREFS_FILE_MODE 0666 #define GIT_HEAD_FILE "HEAD" +#define GIT_FETCH_HEAD_FILE "FETCH_HEAD" #define GIT_MERGE_HEAD_FILE "MERGE_HEAD" #define GIT_REFS_HEADS_MASTER_FILE GIT_REFS_HEADS_DIR "master" +#define GIT_REFNAME_MAX 1024 + struct git_reference { + unsigned int flags; git_repository *owner; char *name; - unsigned int type; time_t mtime; + + union { + git_oid oid; + char *symbolic; + } target; }; typedef struct { - git_hashtable *packfile; - git_hashtable *loose_cache; + git_strmap *packfile; time_t packfile_time; } git_refcache; - void git_repository__refcache_free(git_refcache *refs); -int git_repository__refcache_init(git_refcache *refs); -int git_reference__normalize_name(char *buffer_out, const char *name); -int git_reference__normalize_name_oid(char *buffer_out, const char *name); +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 new file mode 100644 index 000000000..b6b1158b7 --- /dev/null +++ b/src/refspec.c @@ -0,0 +1,129 @@ +/* + * 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 "git2/errors.h" + +#include "common.h" +#include "refspec.h" +#include "util.h" +#include "posix.h" + +int git_refspec_parse(git_refspec *refspec, const char *str) +{ + char *delim; + + memset(refspec, 0x0, sizeof(git_refspec)); + + if (*str == '+') { + refspec->force = 1; + str++; + } + + delim = strchr(str, ':'); + if (delim == NULL) { + refspec->src = git__strdup(str); + GITERR_CHECK_ALLOC(refspec->src); + return 0; + } + + refspec->src = git__strndup(str, delim - str); + GITERR_CHECK_ALLOC(refspec->src); + + refspec->dst = git__strdup(delim + 1); + if (refspec->dst == NULL) { + git__free(refspec->src); + refspec->src = NULL; + return -1; + } + + return 0; +} + +const char *git_refspec_src(const git_refspec *refspec) +{ + return refspec == NULL ? NULL : refspec->src; +} + +const char *git_refspec_dst(const git_refspec *refspec) +{ + return refspec == NULL ? NULL : refspec->dst; +} + +int git_refspec_force(const git_refspec *refspec) +{ + assert(refspec); + + return refspec->force; +} + +int git_refspec_src_matches(const git_refspec *refspec, const char *refname) +{ + if (refspec == NULL || refspec->src == NULL) + return false; + + return (p_fnmatch(refspec->src, refname, 0) == 0); +} + +int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, const char *name) +{ + size_t baselen, namelen; + + baselen = strlen(spec->dst); + if (outlen <= baselen) { + giterr_set(GITERR_INVALID, "Reference name too long"); + return GIT_EBUFS; + } + + /* + * No '*' at the end means that it's mapped to one specific local + * branch, so no actual transformation is needed. + */ + if (spec->dst[baselen - 1] != '*') { + memcpy(out, spec->dst, baselen + 1); /* include '\0' */ + return 0; + } + + /* There's a '*' at the end, so remove its length */ + baselen--; + + /* skip the prefix, -1 is for the '*' */ + name += strlen(spec->src) - 1; + + namelen = strlen(name); + + if (outlen <= baselen + namelen) { + giterr_set(GITERR_INVALID, "Reference name too long"); + return GIT_EBUFS; + } + + memcpy(out, spec->dst, baselen); + memcpy(out + baselen, name, namelen + 1); + + return 0; +} + +int git_refspec_transform_r(git_buf *out, const git_refspec *spec, const char *name) +{ + 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 (git_buf_len(out) > 0 && out->ptr[git_buf_len(out) - 1] != '*') + return 0; + + git_buf_truncate(out, git_buf_len(out) - 1); /* remove trailing '*' */ + git_buf_puts(out, name + strlen(spec->src) - 1); + + if (git_buf_oom(out)) + return -1; + + return 0; +} + diff --git a/src/refspec.h b/src/refspec.h new file mode 100644 index 000000000..2db504910 --- /dev/null +++ b/src/refspec.h @@ -0,0 +1,35 @@ +/* + * 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_refspec_h__ +#define INCLUDE_refspec_h__ + +#include "git2/refspec.h" +#include "buffer.h" + +struct git_refspec { + struct git_refspec *next; + char *src; + char *dst; + unsigned int force :1, + pattern :1, + matching :1; +}; + +int git_refspec_parse(struct git_refspec *refspec, const char *str); + +/** + * Transform a reference to its target following the refspec's rules, + * and writes the results into a git_buf. + * + * @param out where to store the target name + * @param spec the refspec + * @param name the name of the reference to transform + * @return 0 or error if buffer allocation fails + */ +int git_refspec_transform_r(git_buf *out, const git_refspec *spec, const char *name); + +#endif diff --git a/src/remote.c b/src/remote.c new file mode 100644 index 000000000..8d6076107 --- /dev/null +++ b/src/remote.c @@ -0,0 +1,524 @@ +/* + * 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 "git2/remote.h" +#include "git2/config.h" +#include "git2/types.h" + +#include "config.h" +#include "repository.h" +#include "remote.h" +#include "fetch.h" +#include "refs.h" + +#include <regex.h> + +static int refspec_parse(git_refspec *refspec, const char *str) +{ + char *delim; + + memset(refspec, 0x0, sizeof(git_refspec)); + + if (*str == '+') { + refspec->force = 1; + str++; + } + + delim = strchr(str, ':'); + if (delim == NULL) { + giterr_set(GITERR_NET, "Invalid refspec, missing ':'"); + return -1; + } + + refspec->src = git__strndup(str, delim - str); + GITERR_CHECK_ALLOC(refspec->src); + + refspec->dst = git__strdup(delim + 1); + GITERR_CHECK_ALLOC(refspec->dst); + + return 0; +} + +static int parse_remote_refspec(git_config *cfg, git_refspec *refspec, const char *var) +{ + int error; + const char *val; + + if ((error = git_config_get_string(&val, cfg, var)) < 0) + return error; + + return refspec_parse(refspec, val); +} + +int git_remote_new(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch) +{ + git_remote *remote; + + /* name is optional */ + assert(out && repo && url); + + remote = git__malloc(sizeof(git_remote)); + GITERR_CHECK_ALLOC(remote); + + memset(remote, 0x0, sizeof(git_remote)); + remote->repo = repo; + remote->check_cert = 1; + + if (git_vector_init(&remote->refs, 32, NULL) < 0) + return -1; + + remote->url = git__strdup(url); + GITERR_CHECK_ALLOC(remote->url); + + if (name != NULL) { + remote->name = git__strdup(name); + GITERR_CHECK_ALLOC(remote->name); + } + + if (fetch != NULL) { + if (refspec_parse(&remote->fetch, fetch) < 0) + goto on_error; + } + + *out = remote; + return 0; + +on_error: + git_remote_free(remote); + return -1; +} + +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 = 0; + git_config *config; + + assert(out && repo && name); + + if (git_repository_config__weakptr(&config, repo) < 0) + return -1; + + remote = git__malloc(sizeof(git_remote)); + GITERR_CHECK_ALLOC(remote); + + memset(remote, 0x0, sizeof(git_remote)); + remote->check_cert = 1; + remote->name = git__strdup(name); + GITERR_CHECK_ALLOC(remote->name); + + if (git_vector_init(&remote->refs, 32, NULL) < 0) { + error = -1; + goto cleanup; + } + + if (git_buf_printf(&buf, "remote.%s.url", name) < 0) { + error = -1; + goto cleanup; + } + + if ((error = git_config_get_string(&val, config, git_buf_cstr(&buf))) < 0) + goto cleanup; + + remote->repo = repo; + remote->url = git__strdup(val); + GITERR_CHECK_ALLOC(remote->url); + + git_buf_clear(&buf); + if (git_buf_printf(&buf, "remote.%s.fetch", name) < 0) { + error = -1; + goto cleanup; + } + + error = parse_remote_refspec(config, &remote->fetch, git_buf_cstr(&buf)); + if (error == GIT_ENOTFOUND) + error = 0; + + if (error < 0) { + error = -1; + goto cleanup; + } + + git_buf_clear(&buf); + if (git_buf_printf(&buf, "remote.%s.push", name) < 0) { + error = -1; + goto cleanup; + } + + error = parse_remote_refspec(config, &remote->push, git_buf_cstr(&buf)); + if (error == GIT_ENOTFOUND) + error = 0; + + if (error < 0) { + error = -1; + goto cleanup; + } + + *out = remote; + +cleanup: + git_buf_free(&buf); + + if (error < 0) + git_remote_free(remote); + + return error; +} + +int git_remote_save(const git_remote *remote) +{ + git_config *config; + git_buf buf = GIT_BUF_INIT, value = GIT_BUF_INIT; + + if (git_repository_config__weakptr(&config, remote->repo) < 0) + return -1; + + if (git_buf_printf(&buf, "remote.%s.%s", remote->name, "url") < 0) + return -1; + + 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.fetch", remote->name); + if (remote->fetch.force) + git_buf_putc(&value, '+'); + git_buf_printf(&value, "%s:%s", remote->fetch.src, remote->fetch.dst); + if (git_buf_oom(&buf) || git_buf_oom(&value)) + return -1; + + 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.push", remote->name); + if (remote->push.force) + git_buf_putc(&value, '+'); + git_buf_printf(&value, "%s:%s", remote->push.src, remote->push.dst); + if (git_buf_oom(&buf) || git_buf_oom(&value)) + return -1; + + if (git_config_set_string(config, git_buf_cstr(&buf), git_buf_cstr(&value)) < 0) + goto on_error; + } + + git_buf_free(&buf); + git_buf_free(&value); + + return 0; + +on_error: + git_buf_free(&buf); + git_buf_free(&value); + return -1; +} + +const char *git_remote_name(git_remote *remote) +{ + assert(remote); + return remote->name; +} + +const char *git_remote_url(git_remote *remote) +{ + assert(remote); + return remote->url; +} + +int git_remote_set_fetchspec(git_remote *remote, const char *spec) +{ + git_refspec refspec; + + assert(remote && spec); + + 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 0; +} + +const git_refspec *git_remote_fetchspec(git_remote *remote) +{ + assert(remote); + return &remote->fetch; +} + +int git_remote_set_pushspec(git_remote *remote, const char *spec) +{ + git_refspec refspec; + + assert(remote && spec); + + 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 0; +} + +const git_refspec *git_remote_pushspec(git_remote *remote) +{ + assert(remote); + return &remote->push; +} + +int git_remote_connect(git_remote *remote, int direction) +{ + git_transport *t; + + assert(remote); + + if (git_transport_new(&t, remote->url) < 0) + return -1; + + t->check_cert = remote->check_cert; + if (t->connect(t, direction) < 0) { + goto on_error; + } + + remote->transport = t; + + return 0; + +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) { + giterr_set(GITERR_NET, "The remote is not connected"); + return -1; + } + + return remote->transport->ls(remote->transport, list_cb, payload); +} + +int git_remote_download(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats) +{ + int error; + + assert(remote && bytes && stats); + + if ((error = git_fetch_negotiate(remote)) < 0) + return error; + + return git_fetch_download_pack(remote, bytes, stats); +} + +int git_remote_update_tips(git_remote *remote, int (*cb)(const char *refname, const git_oid *a, const git_oid *b)) +{ + 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; + struct git_refspec *spec = &remote->fetch; + + assert(remote); + + if (refs->length == 0) + return 0; + + /* HEAD is only allowed to be the first in the list */ + head = refs->contents[0]; + if (!strcmp(head->name, GIT_HEAD_FILE)) { + 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]; + + 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); + + 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; + +} + +int git_remote_connected(git_remote *remote) +{ + assert(remote); + return remote->transport == NULL ? 0 : remote->transport->connected; +} + +void git_remote_disconnect(git_remote *remote) +{ + assert(remote); + + if (remote->transport != NULL && remote->transport->connected) + remote->transport->close(remote->transport); +} + +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__free(remote); +} + +struct cb_data { + git_vector *list; + regex_t *preg; +}; + +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]; + 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); + GITERR_CHECK_ALLOC(remote_name); + + if (git_vector_insert(data->list, remote_name) < 0) + return -1; + } + + return 0; +} + +int git_remote_list(git_strarray *remotes_list, git_repository *repo) +{ + git_config *cfg; + git_vector list; + regex_t preg; + struct cb_data data; + int error; + + if (git_repository_config__weakptr(&cfg, repo) < 0) + return -1; + + if (git_vector_init(&list, 4, NULL) < 0) + return -1; + + 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 < 0) { + size_t i; + char *elem; + git_vector_foreach(&list, i, elem) { + git__free(elem); + } + + git_vector_free(&list); + return error; + } + + remotes_list->strings = (char **)list.contents; + remotes_list->count = list.length; + + return 0; +} + +int git_remote_add(git_remote **out, git_repository *repo, const char *name, const char *url) +{ + git_buf buf = GIT_BUF_INIT; + + if (git_buf_printf(&buf, "+refs/heads/*:refs/remotes/%s/*", name) < 0) + return -1; + + if (git_remote_new(out, repo, name, url, git_buf_cstr(&buf)) < 0) + goto on_error; + + git_buf_free(&buf); + + if (git_remote_save(*out) < 0) + goto on_error; + + return 0; + +on_error: + git_buf_free(&buf); + git_remote_free(*out); + return -1; +} + +void git_remote_check_cert(git_remote *remote, int check) +{ + assert(remote); + + remote->check_cert = check; +} diff --git a/src/remote.h b/src/remote.h new file mode 100644 index 000000000..0949ad434 --- /dev/null +++ b/src/remote.h @@ -0,0 +1,26 @@ +/* + * 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_remote_h__ +#define INCLUDE_remote_h__ + +#include "refspec.h" +#include "transport.h" +#include "repository.h" + +struct git_remote { + char *name; + char *url; + git_vector refs; + struct git_refspec fetch; + struct git_refspec push; + git_transport *transport; + git_repository *repo; + unsigned int need_pack:1, + check_cert; +}; + +#endif diff --git a/src/repository.c b/src/repository.c index 32ca8dd79..718170839 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1,28 +1,11 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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 <stdarg.h> +#include <ctype.h> #include "git2/object.h" @@ -32,447 +15,930 @@ #include "tag.h" #include "blob.h" #include "fileops.h" - +#include "config.h" #include "refs.h" #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_BRANCH_MASTER "master" -typedef struct { - char *path_repository; - unsigned is_bare:1, has_been_reinit:1; -} repo_init; +#define GIT_REPO_VERSION 0 + +static void drop_odb(git_repository *repo) +{ + if (repo->_odb != NULL) { + GIT_REFCOUNT_OWN(repo->_odb, NULL); + git_odb_free(repo->_odb); + repo->_odb = NULL; + } +} + +static void drop_config(git_repository *repo) +{ + if (repo->_config != NULL) { + GIT_REFCOUNT_OWN(repo->_config, NULL); + git_config_free(repo->_config); + repo->_config = NULL; + } + + git_repository__cvar_cache_clear(repo); +} + +static void drop_index(git_repository *repo) +{ + if (repo->_index != NULL) { + GIT_REFCOUNT_OWN(repo->_index, NULL); + git_index_free(repo->_index); + repo->_index = NULL; + } +} + +void git_repository_free(git_repository *repo) +{ + if (repo == NULL) + return; + + 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); + + drop_config(repo); + drop_index(repo); + drop_odb(repo); + + git__free(repo); +} /* * Git repository open methods * * Open a repository object from its path */ -static int assign_repository_dirs( - git_repository *repo, - const char *git_dir, - const char *git_object_directory, - const char *git_index_file, - const char *git_work_tree) +static bool valid_repository_path(git_buf *repository_path) { - char path_aux[GIT_PATH_MAX]; - int error = GIT_SUCCESS; + /* Check OBJECTS_DIR first, since it will generate the longest path name */ + if (git_path_contains_dir(repository_path, GIT_OBJECTS_DIR) == false) + return false; - assert(repo); + /* Ensure HEAD file exists */ + if (git_path_contains_file(repository_path, GIT_HEAD_FILE) == false) + return false; - if (git_dir == NULL) - return git__throw(GIT_ENOTFOUND, "Failed to open repository. Git dir not found"); + if (git_path_contains_dir(repository_path, GIT_REFS_DIR) == false) + return false; + + return true; +} - error = gitfo_prettify_dir_path(path_aux, sizeof(path_aux), git_dir); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to open repository"); +static git_repository *repository_alloc(void) +{ + git_repository *repo = git__malloc(sizeof(git_repository)); + if (!repo) + return NULL; - /* store GIT_DIR */ - repo->path_repository = git__strdup(path_aux); - if (repo->path_repository == NULL) - return GIT_ENOMEM; + memset(repo, 0x0, sizeof(git_repository)); - /* path to GIT_OBJECT_DIRECTORY */ - if (git_object_directory == NULL) - git__joinpath(path_aux, repo->path_repository, GIT_OBJECTS_DIR); - else { - error = gitfo_prettify_dir_path(path_aux, sizeof(path_aux), git_object_directory); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to open repository"); + if (git_cache_init(&repo->objects, GIT_DEFAULT_CACHE_SIZE, &git_object__free) < 0) { + git__free(repo); + return NULL; } - /* Store GIT_OBJECT_DIRECTORY */ - repo->path_odb = git__strdup(path_aux); - if (repo->path_odb == NULL) - return GIT_ENOMEM; + /* set all the entries in the cvar cache to `unset` */ + git_repository__cvar_cache_clear(repo); - /* path to GIT_WORK_TREE */ - if (git_work_tree == NULL) - repo->is_bare = 1; + return repo; +} + +static int load_config_data(git_repository *repo) +{ + int is_bare; + git_config *config; + + if (git_repository_config__weakptr(&config, repo) < 0) + return -1; + + /* Try to figure out if it's bare, default to non-bare if it's not set */ + if (git_config_get_bool(&is_bare, config, "core.bare") < 0) + repo->is_bare = 0; + else + 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(&worktree, config, "core.worktree"); + if (!error && worktree != NULL) + repo->workdir = git__strdup(worktree); + else if (error != GIT_ENOTFOUND) + return error; else { - error = gitfo_prettify_dir_path(path_aux, sizeof(path_aux), git_work_tree); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to open repository"); - - /* Store GIT_WORK_TREE */ - repo->path_workdir = git__strdup(path_aux); - if (repo->path_workdir == NULL) - return GIT_ENOMEM; - - /* Path to GIT_INDEX_FILE */ - if (git_index_file == NULL) - git__joinpath(path_aux, repo->path_repository, GIT_INDEX_FILE); + giterr_clear(); + + if (parent_path && git_path_isdir(parent_path->ptr)) + repo->workdir = git_buf_detach(parent_path); else { - error = gitfo_prettify_file_path(path_aux, sizeof(path_aux), git_index_file); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to open repository"); + 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; + + if (len == 0 || len >= sizeof(buf) || git_path_root(ceil) == -1) + continue; + + strncpy(buf, ceil, len); + buf[len] = '\0'; - /* store GIT_INDEX_FILE */ - repo->path_index = git__strdup(path_aux); - if (repo->path_index == NULL) - return GIT_ENOMEM; + 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 GIT_SUCCESS; + + return (int)(max_len <= min_len ? min_len : max_len); } -static int check_repository_dirs(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) { - char path_aux[GIT_PATH_MAX]; + int error = 0; + git_buf file = GIT_BUF_INIT; + size_t prefix_len = strlen(GIT_FILE_CONTENT_PREFIX); - if (gitfo_isdir(repo->path_repository) < GIT_SUCCESS) - return git__throw(GIT_ENOTAREPO, "`%s` is not a folder", repo->path_repository); + assert(path_out && file_path); - /* Ensure GIT_OBJECT_DIRECTORY exists */ - if (gitfo_isdir(repo->path_odb) < GIT_SUCCESS) - return git__throw(GIT_ENOTAREPO, "`%s` does not exist", repo->path_odb); + if (git_futils_readbuffer(&file, file_path) < 0) + return -1; - /* Ensure HEAD file exists */ - git__joinpath(path_aux, repo->path_repository, GIT_HEAD_FILE); - if (gitfo_exists(path_aux) < 0) - return git__throw(GIT_ENOTAREPO, "HEAD file is missing"); + git_buf_rtrim(&file); - return GIT_SUCCESS; + 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 && git__isspace(*gitlink)) gitlink++; + error = git_path_prettify_dir(path_out, gitlink, path_out->ptr); + } + + git_buf_free(&file); + return error; } -static int guess_repository_dirs(git_repository *repo, const char *repository_path) +static int find_repo( + git_buf *repo_path, + git_buf *parent_path, + const char *start_path, + uint32_t flags, + const char *ceiling_dirs) { - char buffer[GIT_PATH_MAX]; - const char *path_work_tree = 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; + + 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); + } + } - /* Git directory name */ - if (git__basename_r(buffer, sizeof(buffer), repository_path) < 0) - return git__throw(GIT_EINVALIDPATH, "Unable to parse folder name from `%s`", repository_path); + /* move up one directory level */ + if (git_path_dirname_r(&path, path.ptr) < 0) { + error = -1; + break; + } - if (strcmp(buffer, DOT_GIT) == 0) { - /* Path to working dir */ - if (git__dirname_r(buffer, sizeof(buffer), repository_path) < 0) - return git__throw(GIT_EINVALIDPATH, "Unable to parse parent folder name from `%s`", repository_path); - path_work_tree = buffer; + 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; } - return assign_repository_dirs(repo, repository_path, NULL, NULL, path_work_tree); + 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; + } + + 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; + } + + return error; } -static git_repository *repository_alloc() +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; - git_repository *repo = git__malloc(sizeof(git_repository)); - if (!repo) - return NULL; + *repo_ptr = NULL; - memset(repo, 0x0, sizeof(git_repository)); + if ((error = find_repo(&path, &parent, start_path, flags, ceiling_dirs)) < 0) + return error; - error = git_cache_init(&repo->objects, GIT_DEFAULT_CACHE_SIZE, &git_object__free); - if (error < GIT_SUCCESS) { - free(repo); - return NULL; + 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; } - if (git_repository__refcache_init(&repo->references) < GIT_SUCCESS) { - free(repo); - return NULL; + 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; + int error; + + assert(start_path && repository_path && size > 0); + + *repository_path = '\0'; + + if ((error = find_repo(&path, NULL, start_path, flags, ceiling_dirs)) < 0) + return error != GIT_ENOTFOUND ? -1 : error; + + 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; } - return repo; + /* success: we discovered a repository */ + git_buf_copy_cstr(repository_path, size, &path); + git_buf_free(&path); + return 0; } -static int init_odb(git_repository *repo) +static int load_config( + git_config **out, + git_repository *repo, + const char *global_config_path, + const char *system_config_path) { - return git_odb_open(&repo->db, repo->path_odb); /* TODO: Move odb.c to new error handling */ + git_buf config_path = GIT_BUF_INIT; + git_config *cfg = NULL; + + assert(repo && out); + + 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; + + if (git_config_add_file_ondisk(cfg, config_path.ptr, 3) < 0) + goto on_error; + + git_buf_free(&config_path); + + if (global_config_path != NULL) { + if (git_config_add_file_ondisk(cfg, global_config_path, 2) < 0) + goto on_error; + } + + if (system_config_path != NULL) { + if (git_config_add_file_ondisk(cfg, system_config_path, 1) < 0) + goto on_error; + } + + *out = cfg; + return 0; + +on_error: + git_buf_free(&config_path); + git_config_free(cfg); + *out = NULL; + return -1; } -int git_repository_open3(git_repository **repo_out, - const char *git_dir, - git_odb *object_database, - const char *git_index_file, - const char *git_work_tree) +int git_repository_config__weakptr(git_config **out, git_repository *repo) { - git_repository *repo; - int error = GIT_SUCCESS; + if (repo->_config == NULL) { + git_buf global_buf = GIT_BUF_INIT, system_buf = GIT_BUF_INIT; + int res; - assert(repo_out); + const char *global_config_path = NULL; + const char *system_config_path = NULL; - if (object_database == NULL) - return git__throw(GIT_EINVALIDARGS, "Failed to open repository. `object_database` can't be null"); + if (git_config_find_global_r(&global_buf) == 0) + global_config_path = global_buf.ptr; - repo = repository_alloc(); - if (repo == NULL) - return GIT_ENOMEM; + if (git_config_find_system_r(&system_buf) == 0) + system_config_path = system_buf.ptr; - error = assign_repository_dirs(repo, - git_dir, - NULL, - git_index_file, - git_work_tree); + res = load_config(&repo->_config, repo, global_config_path, system_config_path); - if (error < GIT_SUCCESS) - goto cleanup; + git_buf_free(&global_buf); + git_buf_free(&system_buf); - error = check_repository_dirs(repo); - if (error < GIT_SUCCESS) - goto cleanup; + if (res < 0) + return -1; - repo->db = object_database; + GIT_REFCOUNT_OWN(repo->_config, repo); + } - *repo_out = repo; - return GIT_SUCCESS; + *out = repo->_config; + return 0; +} -cleanup: - git_repository_free(repo); - return git__rethrow(error, "Failed to open repository"); +int git_repository_config(git_config **out, git_repository *repo) +{ + if (git_repository_config__weakptr(out, repo) < 0) + return -1; + + GIT_REFCOUNT_INC(*out); + return 0; } +void git_repository_set_config(git_repository *repo, git_config *config) +{ + assert(repo && config); + + drop_config(repo); + + repo->_config = config; + GIT_REFCOUNT_OWN(repo->_config, repo); +} -int git_repository_open2(git_repository **repo_out, - const char *git_dir, - const char *git_object_directory, - const char *git_index_file, - const char *git_work_tree) +int git_repository_odb__weakptr(git_odb **out, git_repository *repo) { - git_repository *repo; - int error = GIT_SUCCESS; + assert(repo && out); - assert(repo_out); + if (repo->_odb == NULL) { + git_buf odb_path = GIT_BUF_INIT; + int res; - repo = repository_alloc(); - if (repo == NULL) - return GIT_ENOMEM; + if (git_buf_joinpath(&odb_path, repo->path_repository, GIT_OBJECTS_DIR) < 0) + return -1; - error = assign_repository_dirs(repo, - git_dir, - git_object_directory, - git_index_file, - git_work_tree); + res = git_odb_open(&repo->_odb, odb_path.ptr); + git_buf_free(&odb_path); /* done with path */ - if (error < GIT_SUCCESS) - goto cleanup; + if (res < 0) + return -1; - error = check_repository_dirs(repo); - if (error < GIT_SUCCESS) - goto cleanup; + GIT_REFCOUNT_OWN(repo->_odb, repo); + } - error = init_odb(repo); - if (error < GIT_SUCCESS) - goto cleanup; + *out = repo->_odb; + return 0; +} - *repo_out = repo; - return GIT_SUCCESS; +int git_repository_odb(git_odb **out, git_repository *repo) +{ + if (git_repository_odb__weakptr(out, repo) < 0) + return -1; -cleanup: - git_repository_free(repo); - return git__rethrow(error, "Failed to open repository"); + GIT_REFCOUNT_INC(*out); + return 0; } -int git_repository_open(git_repository **repo_out, const char *path) +void git_repository_set_odb(git_repository *repo, git_odb *odb) { - git_repository *repo; - int error = GIT_SUCCESS; + assert(repo && odb); - assert(repo_out && path); + drop_odb(repo); - repo = repository_alloc(); - if (repo == NULL) - return GIT_ENOMEM; + repo->_odb = odb; + GIT_REFCOUNT_OWN(repo->_odb, repo); + GIT_REFCOUNT_INC(odb); +} - error = guess_repository_dirs(repo, path); - if (error < GIT_SUCCESS) - goto cleanup; +int git_repository_index__weakptr(git_index **out, git_repository *repo) +{ + assert(out && repo); - error = check_repository_dirs(repo); - if (error < GIT_SUCCESS) - goto cleanup; + if (repo->_index == NULL) { + int res; + git_buf index_path = GIT_BUF_INIT; - error = init_odb(repo); - if (error < GIT_SUCCESS) - goto cleanup; + if (git_buf_joinpath(&index_path, repo->path_repository, GIT_INDEX_FILE) < 0) + return -1; - *repo_out = repo; - return GIT_SUCCESS; + res = git_index_open(&repo->_index, index_path.ptr); + git_buf_free(&index_path); /* done with path */ -cleanup: - git_repository_free(repo); - return git__rethrow(error, "Failed to open repository"); + if (res < 0) + return -1; + + GIT_REFCOUNT_OWN(repo->_index, repo); + } + + *out = repo->_index; + return 0; } -void git_repository_free(git_repository *repo) +int git_repository_index(git_index **out, git_repository *repo) { - if (repo == NULL) - return; + if (git_repository_index__weakptr(out, repo) < 0) + return -1; - git_cache_free(&repo->objects); - git_repository__refcache_free(&repo->references); + GIT_REFCOUNT_INC(*out); + return 0; +} - free(repo->path_workdir); - free(repo->path_index); - free(repo->path_repository); - free(repo->path_odb); +void git_repository_set_index(git_repository *repo, git_index *index) +{ + assert(repo && index); - if (repo->db != NULL) - git_odb_close(repo->db); + drop_index(repo); - free(repo); + repo->_index = index; + GIT_REFCOUNT_OWN(repo->_index, repo); + GIT_REFCOUNT_INC(index); } -git_odb *git_repository_database(git_repository *repo) +static int check_repositoryformatversion(git_repository *repo) { - assert(repo); - return repo->db; + git_config *config; + int version; + + if (git_repository_config__weakptr(&config, repo) < 0) + return -1; + + if (git_config_get_int32(&version, config, "core.repositoryformatversion") < 0) + return -1; + + if (GIT_REPO_VERSION < version) { + giterr_set(GITERR_REPOSITORY, + "Unsupported repository version %d. Only versions up to %d are supported.", + version, GIT_REPO_VERSION); + return -1; + } + + return 0; } -static int repo_init_reinit(repo_init *results) +static int repo_init_reinit(git_repository **repo_out, const char *repository_path, int is_bare) { - /* TODO: reinit the repository */ - results->has_been_reinit = 1; - return git__throw(GIT_ENOTIMPLEMENTED, "Failed to reinitialize the repository. This feature is not yet implemented"); + git_repository *repo = NULL; + + GIT_UNUSED(is_bare); + + if (git_repository_open(&repo, repository_path) < 0) + return -1; + + if (check_repositoryformatversion(repo) < 0) { + git_repository_free(repo); + return -1; + } + + /* TODO: reinitialize the templates */ + + *repo_out = repo; + return 0; } -static int repo_init_createhead(git_repository *repo) +static int repo_init_createhead(const char *git_dir) { - git_reference *head_reference; - return git_reference_create_symbolic(&head_reference, repo, GIT_HEAD_FILE, GIT_REFS_HEADS_MASTER_FILE); /* TODO: finalize moving refs.c to new error handling */ + git_buf ref_path = GIT_BUF_INIT; + git_filebuf ref = GIT_FILEBUF_INIT; + + 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 0; } -static int repo_init_check_head_existence(char * repository_path) +static bool is_chmod_supported(const char *file_path) { - char temp_path[GIT_PATH_MAX]; + struct stat st1, st2; + static int _is_supported = -1; + + if (_is_supported > -1) + return _is_supported; + + if (p_stat(file_path, &st1) < 0) + return false; + + if (p_chmod(file_path, st1.st_mode ^ S_IXUSR) < 0) + return false; + + if (p_stat(file_path, &st2) < 0) + return false; - git__joinpath(temp_path, repository_path, GIT_HEAD_FILE); - return gitfo_exists(temp_path); + _is_supported = (st1.st_mode != st2.st_mode); + return _is_supported; } -static int repo_init_structure(repo_init *results) +static bool is_filesystem_case_insensitive(const char *gitdir_path) { - const int mode = 0755; /* or 0777 ? */ - int error; + git_buf path = GIT_BUF_INIT; + static int _is_insensitive = -1; + + if (_is_insensitive > -1) + return _is_insensitive; - char temp_path[GIT_PATH_MAX]; - char *git_dir = results->path_repository; + if (git_buf_joinpath(&path, gitdir_path, "CoNfIg") < 0) + goto cleanup; - if (gitfo_mkdir_recurs(git_dir, mode)) - return git__throw(GIT_ERROR, "Failed to initialize repository structure. Could not mkdir"); + _is_insensitive = git_path_exists(git_buf_cstr(&path)); - /* Creates the '/objects/info/' directory */ - git__joinpath(temp_path, git_dir, GIT_OBJECTS_INFO_DIR); - error = gitfo_mkdir_recurs(temp_path, mode); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to initialize repository structure"); +cleanup: + git_buf_free(&path); + return _is_insensitive; +} - /* Creates the '/objects/pack/' directory */ - git__joinpath(temp_path, git_dir, GIT_OBJECTS_PACK_DIR); - error = gitfo_mkdir(temp_path, mode); - if (error < GIT_SUCCESS) - return git__throw(error, "Unable to create `%s` folder", temp_path); +static int repo_init_config(const char *git_dir, bool is_bare, bool is_reinit) +{ + git_buf cfg_path = GIT_BUF_INIT; + git_config *config = NULL; + +#define SET_REPO_CONFIG(type, name, val) {\ + if (git_config_set_##type(config, name, val) < 0) { \ + git_buf_free(&cfg_path); \ + git_config_free(config); \ + return -1; } \ +} - /* Creates the '/refs/heads/' directory */ - git__joinpath(temp_path, git_dir, GIT_REFS_HEADS_DIR); - error = gitfo_mkdir_recurs(temp_path, mode); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to initialize repository structure"); + if (git_buf_joinpath(&cfg_path, git_dir, GIT_CONFIG_FILENAME_INREPO) < 0) + return -1; - /* Creates the '/refs/tags/' directory */ - git__joinpath(temp_path, git_dir, GIT_REFS_TAGS_DIR); - error = gitfo_mkdir(temp_path, mode); - if (error < GIT_SUCCESS) - return git__throw(error, "Unable to create `%s` folder", temp_path); + if (git_config_open_ondisk(&config, git_buf_cstr(&cfg_path)) < 0) { + git_buf_free(&cfg_path); + return -1; + } - /* TODO: what's left? templates? */ + SET_REPO_CONFIG(bool, "core.bare", is_bare); + SET_REPO_CONFIG(int32, "core.repositoryformatversion", GIT_REPO_VERSION); + SET_REPO_CONFIG(bool, "core.filemode", is_chmod_supported(git_buf_cstr(&cfg_path))); + + if (!is_reinit && is_filesystem_case_insensitive(git_dir)) + SET_REPO_CONFIG(bool, "core.ignorecase", true); + /* TODO: what other defaults? */ - return GIT_SUCCESS; + git_buf_free(&cfg_path); + git_config_free(config); + return 0; } -static int repo_init_find_dir(repo_init *results, const char* path) +#define GIT_HOOKS_DIR "hooks/" +#define GIT_HOOKS_DIR_MODE 0755 + +#define GIT_HOOKS_README_FILE GIT_HOOKS_DIR "README.sample" +#define GIT_HOOKS_README_MODE 0755 +#define GIT_HOOKS_README_CONTENT \ +"#!/bin/sh\n"\ +"#\n"\ +"# Place appropriately named executable hook scripts into this directory\n"\ +"# to intercept various actions that git takes. See `git help hooks` for\n"\ +"# more information.\n" + +#define GIT_INFO_DIR "info/" +#define GIT_INFO_DIR_MODE 0755 + +#define GIT_INFO_EXCLUDE_FILE GIT_INFO_DIR "exclude" +#define GIT_INFO_EXCLUDE_MODE 0644 +#define GIT_INFO_EXCLUDE_CONTENT \ +"# File patterns to ignore; see `git help ignore` for more information.\n"\ +"# Lines that start with '#' are comments.\n" + +#define GIT_DESC_FILE "description" +#define GIT_DESC_MODE 0644 +#define GIT_DESC_CONTENT "Unnamed repository; edit this file 'description' to name the repository.\n" + +static int repo_write_template( + const char *git_dir, const char *file, mode_t mode, const char *content) { - char temp_path[GIT_PATH_MAX]; - int error = GIT_SUCCESS; + git_buf path = GIT_BUF_INIT; + int fd, error = 0; + + if (git_buf_joinpath(&path, git_dir, file) < 0) + return -1; + + fd = p_open(git_buf_cstr(&path), O_WRONLY | O_CREAT | O_EXCL, mode); - error = gitfo_prettify_dir_path(temp_path, sizeof(temp_path), path); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to find directory to initialize repository"); + if (fd >= 0) { + error = p_write(fd, content, strlen(content)); - if (!results->is_bare) { - git__joinpath(temp_path, temp_path, GIT_DIR); + p_close(fd); } + else if (errno != EEXIST) + error = fd; - results->path_repository = git__strdup(temp_path); - if (results->path_repository == NULL) - return GIT_ENOMEM; + git_buf_free(&path); - return GIT_SUCCESS; + if (error) + giterr_set(GITERR_OS, + "Failed to initialize repository with template '%s'", file); + + return error; +} + +static int repo_init_structure(const char *git_dir, int is_bare) +{ + 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/' */ + { GIT_REFS_HEADS_DIR, GIT_REFS_DIR_MODE }, /* '/refs/heads/' */ + { GIT_REFS_TAGS_DIR, GIT_REFS_DIR_MODE }, /* '/refs/tags/' */ + { GIT_HOOKS_DIR, GIT_HOOKS_DIR_MODE }, /* '/hooks/' */ + { GIT_INFO_DIR, GIT_INFO_DIR_MODE }, /* '/info/' */ + { NULL, 0 } + }; + struct { const char *file; mode_t mode; const char *content; } tmpl[] = { + { GIT_DESC_FILE, GIT_DESC_MODE, GIT_DESC_CONTENT }, + { GIT_HOOKS_README_FILE, GIT_HOOKS_README_MODE, GIT_HOOKS_README_CONTENT }, + { GIT_INFO_EXCLUDE_FILE, GIT_INFO_EXCLUDE_MODE, GIT_INFO_EXCLUDE_CONTENT }, + { NULL, 0, NULL } + }; + + /* Make the base directory */ + 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 + 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) { + if (git_futils_mkdir_r(dirs[i].dir, git_dir, dirs[i].mode) < 0) + return -1; + } + + /* Make template files as needed */ + for (i = 0; tmpl[i].file != NULL; ++i) { + if (repo_write_template( + git_dir, tmpl[i].file, tmpl[i].mode, tmpl[i].content) < 0) + return -1; + } + + return 0; } int git_repository_init(git_repository **repo_out, const char *path, unsigned is_bare) { - int error = GIT_SUCCESS; - git_repository *repo = NULL; - repo_init results; - - assert(repo_out && path); + git_buf repository_path = GIT_BUF_INIT; + bool is_reinit; + int result = -1; - results.path_repository = NULL; - results.is_bare = is_bare; + assert(repo_out && path); - error = repo_init_find_dir(&results, path); - if (error < GIT_SUCCESS) + if (git_buf_joinpath(&repository_path, path, is_bare ? "" : GIT_DIR) < 0) goto cleanup; - if (!repo_init_check_head_existence(results.path_repository)) - return repo_init_reinit(&results); + is_reinit = git_path_isdir(repository_path.ptr) && valid_repository_path(&repository_path); - error = repo_init_structure(&results); - if (error < GIT_SUCCESS) - goto cleanup; + if (is_reinit) { + if (repo_init_reinit(repo_out, repository_path.ptr, is_bare) < 0) + goto cleanup; - repo = repository_alloc(); - if (repo == NULL) { - error = GIT_ENOMEM; - goto cleanup; + result = repo_init_config(repository_path.ptr, is_bare, is_reinit); } - error = guess_repository_dirs(repo, results.path_repository); - if (error < GIT_SUCCESS) + if (repo_init_structure(repository_path.ptr, is_bare) < 0 || + repo_init_config(repository_path.ptr, is_bare, is_reinit) < 0 || + repo_init_createhead(repository_path.ptr) < 0 || + git_repository_open(repo_out, repository_path.ptr) < 0) { goto cleanup; + } - assert(repo->is_bare == is_bare); + result = 0; - error = init_odb(repo); - if (error < GIT_SUCCESS) - goto cleanup; +cleanup: + git_buf_free(&repository_path); + return result; +} - error = repo_init_createhead(repo); - if (error < GIT_SUCCESS) - goto cleanup; +int git_repository_head_detached(git_repository *repo) +{ + git_reference *ref; + git_odb *odb = NULL; + int exists; - /* should never fail */ - assert(check_repository_dirs(repo) == GIT_SUCCESS); + if (git_repository_odb__weakptr(&odb, repo) < 0) + return -1; - free(results.path_repository); - *repo_out = repo; - return GIT_SUCCESS; + if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0) + return -1; -cleanup: - free(results.path_repository); - git_repository_free(repo); - return git__rethrow(error, "Failed to (re)init the repository `%s`", path); + if (git_reference_type(ref) == GIT_REF_SYMBOLIC) { + git_reference_free(ref); + return 0; + } + + exists = git_odb_exists(odb, git_reference_oid(ref)); + + git_reference_free(ref); + return exists; +} + +int git_repository_head(git_reference **head_out, git_repository *repo) +{ + return git_reference_lookup_resolved(head_out, repo, GIT_HEAD_FILE, -1); +} + +int git_repository_head_orphan(git_repository *repo) +{ + git_reference *ref = NULL; + int error; + + error = git_repository_head(&ref, repo); + git_reference_free(ref); + + if (error == GIT_ENOTFOUND) + return 1; + + if (error < 0) + return -1; + + return 0; } int git_repository_is_empty(git_repository *repo) { - git_reference *head, *branch; + git_reference *head = NULL, *branch = NULL; int error; - error = git_reference_lookup(&head, repo, "HEAD"); - if (error < GIT_SUCCESS) - return git__throw(error, "Failed to determine the emptiness of the repository. An error occured while retrieving the HEAD reference"); + if (git_reference_lookup(&head, repo, "HEAD") < 0) + return -1; + + if (git_reference_type(head) != GIT_REF_SYMBOLIC) { + git_reference_free(head); + return 0; + } + + if (strcmp(git_reference_target(head), "refs/heads/master") != 0) { + git_reference_free(head); + return 0; + } + + error = git_reference_resolve(&branch, head); + + git_reference_free(head); + git_reference_free(branch); + + if (error == GIT_ENOTFOUND) + return 1; - if (git_reference_type(head) != GIT_REF_SYMBOLIC) - return git__throw(GIT_EOBJCORRUPTED, "Failed to determine the emptiness of the repository. HEAD is probably in detached state"); + if (error < 0) + return -1; - return git_reference_resolve(&branch, head) == GIT_SUCCESS ? 0 : 1; + return 0; } const char *git_repository_path(git_repository *repo) @@ -484,7 +950,27 @@ const char *git_repository_path(git_repository *repo) const char *git_repository_workdir(git_repository *repo) { assert(repo); - return repo->path_workdir; + + if (repo->is_bare) + return NULL; + + return repo->workdir; +} + +int git_repository_set_workdir(git_repository *repo, const char *workdir) +{ + git_buf path = GIT_BUF_INIT; + + assert(repo && workdir); + + if (git_path_prettify_dir(&path, workdir, NULL) < 0) + return -1; + + git__free(repo->workdir); + + repo->workdir = git_buf_detach(&path); + repo->is_bare = 0; + return 0; } int git_repository_is_bare(git_repository *repo) @@ -492,3 +978,23 @@ int git_repository_is_bare(git_repository *repo) assert(repo); return repo->is_bare; } + +int git_repository_head_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 || + git_object__resolve_to_type(&obj, GIT_OBJ_TREE) < 0) + return -1; + + *tree = (git_tree *)obj; + return 0; +} diff --git a/src/repository.h b/src/repository.h index bcf9b2bc8..91c69a655 100644 --- a/src/repository.h +++ b/src/repository.h @@ -1,3 +1,9 @@ +/* + * 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_repository_h__ #define INCLUDE_repository_h__ @@ -7,16 +13,62 @@ #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 "/" -#define GIT_OBJECTS_DIR "objects/" -#define GIT_INDEX_FILE "index" +#define GIT_DIR_MODE 0755 +#define GIT_BARE_DIR_MODE 0777 +/** Cvar cache identifiers */ +typedef enum { + GIT_CVAR_AUTO_CRLF = 0, /* core.autocrlf */ + GIT_CVAR_EOL, /* core.eol */ + GIT_CVAR_CACHE_MAX +} git_cvar_cached; + +/** + * CVAR value enumerations + * + * These are the values that are actually stored in the cvar cache, instead + * of their string equivalents. These values are internal and symbolic; + * make sure that none of them is set to `-1`, since that is the unique + * identifier for "not cached" + */ +typedef enum { + /* The value hasn't been loaded from the cache yet */ + GIT_CVAR_NOT_CACHED = -1, + + /* core.safecrlf: false, 'fail', 'warn' */ + GIT_SAFE_CRLF_FALSE = 0, + GIT_SAFE_CRLF_FAIL = 1, + GIT_SAFE_CRLF_WARN = 2, + + /* core.autocrlf: false, true, 'input; */ + GIT_AUTO_CRLF_FALSE = 0, + GIT_AUTO_CRLF_TRUE = 1, + GIT_AUTO_CRLF_INPUT = 2, + GIT_AUTO_CRLF_DEFAULT = GIT_AUTO_CRLF_FALSE, + + /* core.eol: unset, 'crlf', 'lf', 'native' */ + GIT_EOL_UNSET = 0, + GIT_EOL_CRLF = 1, + GIT_EOL_LF = 2, +#ifdef GIT_WIN32 + GIT_EOL_NATIVE = GIT_EOL_CRLF, +#else + GIT_EOL_NATIVE = GIT_EOL_LF, +#endif + GIT_EOL_DEFAULT = GIT_EOL_NATIVE +} git_cvar_value; + +/** Base git object for inheritance */ struct git_object { git_cached_obj cached; git_repository *repo; @@ -24,25 +76,63 @@ struct git_object { }; struct git_repository { - git_odb *db; + git_odb *_odb; + git_config *_config; + git_index *_index; git_cache objects; git_refcache references; + git_attr_cache attrcache; + git_strmap *submodules; char *path_repository; - char *path_index; - char *path_odb; - char *path_workdir; + char *workdir; unsigned is_bare:1; unsigned int lru_counter; + + git_cvar_value cvar_cache[GIT_CVAR_CACHE_MAX]; }; /* fully free the object; internal method, do not * export */ void git_object__free(void *object); -int git__parse_oid(git_oid *oid, const char **buffer_out, const char *buffer_end, const char *header); -int git__write_oid(git_odb_stream *src, const char *header, const git_oid *oid); +int git_object__resolve_to_type(git_object **obj, git_otype type); + +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; +} + +int git_repository_head_tree(git_tree **tree, git_repository *repo); + +/* + * Weak pointers to repository internals. + * + * The returned pointers do not need to be freed. Do not keep + * permanent references to these (i.e. between API calls), since they may + * become invalidated if the user replaces a repository internal. + */ +int git_repository_config__weakptr(git_config **out, git_repository *repo); +int git_repository_odb__weakptr(git_odb **out, git_repository *repo); +int git_repository_index__weakptr(git_index **out, git_repository *repo); + +/* + * CVAR cache + * + * Efficient access to the most used config variables of a repository. + * The cache is cleared everytime the config backend is replaced. + */ +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 b64b0e2c0..e64d93f20 100644 --- a/src/revwalk.c +++ b/src/revwalk.c @@ -1,35 +1,28 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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 "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; @@ -37,7 +30,8 @@ typedef struct commit_object { unsigned int seen:1, uninteresting:1, topo_delay:1, - parsed:1; + parsed:1, + flags : 4; unsigned short in_degree; unsigned short out_degree; @@ -52,8 +46,10 @@ typedef struct commit_list { 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; @@ -63,110 +59,102 @@ 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; }; -commit_list *commit_list_insert(commit_object *item, commit_list **list_p) +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; } -void commit_list_free(commit_list **list_p) +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; while (list) { commit_list *temp = list; list = temp->next; - free(temp); + git__free(temp); } *list_p = NULL; } -commit_object *commit_list_pop(commit_list **stack) +static commit_object *commit_list_pop(commit_list **stack) { commit_list *top = *stack; commit_object *item = top ? top->item : NULL; if (top) { *stack = top->next; - free(top); + git__free(top); } 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; - git_oid *id; - - id = (git_oid *)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) @@ -174,66 +162,70 @@ 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) { - 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; unsigned char *parents_start; int i, parents = 0; - long commit_time; + int commit_time; - buffer += STRLEN("tree ") + GIT_OID_HEXSZ + 1; + buffer += strlen("tree ") + GIT_OID_HEXSZ + 1; parents_start = buffer; - while (buffer + parent_len < buffer_end && memcmp(buffer, "parent ", STRLEN("parent ")) == 0) { + while (buffer + parent_len < buffer_end && memcmp(buffer, "parent ", strlen("parent ")) == 0) { parents++; 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_mkstr(&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) @@ -242,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->repo->db, &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_close(obj); - return git__throw(GIT_EOBJTYPE, "Failed to parse commit. Object is no commit object"); + git_odb_object_free(obj); + 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_close(obj); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse commit"); + git_odb_object_free(obj); + 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)) < 0) + 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) @@ -264,25 +396,29 @@ 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]); } -static int process_commit(git_revwalk *walk, commit_object *commit) +static int process_commit(git_revwalk *walk, commit_object *commit, int hide) { int error; + if (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 (commit->uninteresting) - mark_uninteresting(commit); + if ((error = commit_parse(walk, commit)) < 0) + return error; return walk->enqueue(walk, commit); } @@ -290,13 +426,12 @@ static int process_commit(git_revwalk *walk, commit_object *commit) 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) { - error = process_commit(walk, commit->parents[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) @@ -305,11 +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 */ commit->uninteresting = uninteresting; + if (walk->one == NULL && !uninteresting) { + walk->one = commit; + } else { + if (git_vector_insert(&walk->twos, commit) < 0) + return -1; + } - return process_commit(walk, commit); + return 0; } int git_revwalk_push(git_revwalk *walk, const git_oid *oid) @@ -318,12 +459,122 @@ int git_revwalk_push(git_revwalk *walk, const git_oid *oid) return push_commit(walk, oid, 0); } + int git_revwalk_hide(git_revwalk *walk, const git_oid *oid) { assert(walk && 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; + int hide; +}; + +static int push_glob_cb(const char *refname, void *data_) +{ + struct push_cb_data *data = (struct push_cb_data *)data_; + + if (!p_fnmatch(data->glob, refname, 0)) + return push_ref(data->walk, refname, data->hide); + + 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; + regex_t preg; + + assert(walk && glob); + + /* refs/ is implied if not given in the glob */ + if (strncmp(glob, GIT_REFS_DIR, strlen(GIT_REFS_DIR))) { + git_buf_printf(&buf, GIT_REFS_DIR "%s", glob); + } else { + git_buf_puts(&buf, glob); + } + + /* If no '?', '*' or '[' exist, we append '/ *' to the glob */ + memset(&preg, 0x0, sizeof(regex_t)); + if (regcomp(&preg, "[?*[]", REG_EXTENDED)) { + 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)) + goto on_error; + + data.walk = walk; + data.glob = git_buf_cstr(&buf); + data.hide = hide; + + if (git_reference_foreach( + walk->repo, GIT_REF_LISTALL, push_glob_cb, &data) < 0) + goto on_error; + + regfree(&preg); + git_buf_free(&buf); + return 0; + +on_error: + regfree(&preg); + git_buf_free(&buf); + return -1; +} + +int git_revwalk_push_glob(git_revwalk *walk, const char *glob) +{ + assert(walk && glob); + return push_glob(walk, glob, 0); +} + +int git_revwalk_hide_glob(git_revwalk *walk, const char *glob) +{ + assert(walk && glob); + return push_glob(walk, glob, 1); +} + +int git_revwalk_push_head(git_revwalk *walk) +{ + assert(walk); + return push_ref(walk, GIT_HEAD_FILE, 0); +} + +int git_revwalk_hide_head(git_revwalk *walk) +{ + assert(walk); + 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) { return git_pqueue_insert(&walk->iterator_time, commit); @@ -331,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) @@ -340,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_REVWALKOVER; } static int revwalk_next_unsorted(commit_object **object_out, git_revwalk *walk) @@ -358,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_REVWALKOVER; } static int revwalk_next_toposort(commit_object **object_out, git_revwalk *walk) @@ -378,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_REVWALKOVER; if (next->in_degree > 0) { next->topo_delay = 1; @@ -390,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_REVWALKOVER; } 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_REVWALKOVER; + + /* 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"); + if (error != GIT_REVWALKOVER) + 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"); + if (error != GIT_REVWALKOVER) + return error; walk->get_next = &revwalk_next_reverse; } walk->walking = 1; - return GIT_SUCCESS; + return 0; } @@ -453,60 +729,46 @@ int git_revwalk_new(git_revwalk **revwalk_out, git_repository *repo) 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) { - 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; + if (git_repository_odb(&walk->odb, repo) < 0) { + git_revwalk_free(walk); + return -1; + } + *revwalk_out = walk; - return GIT_SUCCESS; + return 0; } void git_revwalk_free(git_revwalk *walk) { - unsigned int i; - const void *GIT_UNUSED(_unused); - 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(walk->commits, _unused, commit, { - if (commit->out_degree > PARENTS_PER_COMMIT) - 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) - free(git_vector_get(&walk->memory_alloc, i)); - - git_vector_free(&walk->memory_alloc); - free(walk); + git_vector_free(&walk->twos); + git__free(walk); } git_repository *git_revwalk_repository(git_revwalk *walk) @@ -541,38 +803,43 @@ 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); - if (error < GIT_SUCCESS) { - if (error == GIT_EREVWALKOVER) - git_revwalk_reset(walk); - return git__rethrow(error, "Failed to load next revision"); + + if (error == GIT_REVWALKOVER) { + git_revwalk_reset(walk); + return GIT_REVWALKOVER; } - git_oid_cpy(oid, &next->oid); - return GIT_SUCCESS; + if (!error) + git_oid_cpy(oid, &next->oid); + + return error; } void git_revwalk_reset(git_revwalk *walk) { - const void *GIT_UNUSED(_unused); commit_object *commit; assert(walk); - GIT_HASHTABLE_FOREACH(walk->commits, _unused, 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); commit_list_free(&walk->iterator_rand); commit_list_free(&walk->iterator_reverse); walk->walking = 0; + + walk->one = NULL; + git_vector_clear(&walk->twos); } diff --git a/src/block-sha1/sha1.c b/src/sha1.c index 8c1460102..8aaedeb8f 100644 --- a/src/block-sha1/sha1.c +++ b/src/sha1.c @@ -1,9 +1,8 @@ /* - * SHA1 routine optimized to do word accesses rather than byte accesses, - * and to avoid unnecessary copies into the context array. + * Copyright (C) 2009-2012 the libgit2 contributors * - * This was initially based on the Mozilla SHA1 implementation, although - * none of the original Mozilla code remains. + * 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" @@ -53,11 +52,11 @@ */ #if defined(__i386__) || defined(__x86_64__) - #define setW(x, val) (*(volatile unsigned int *)&W(x) = (val)) + #define setW(x, val) (*(volatile unsigned int *)&W(x) = (val)) #elif defined(__GNUC__) && defined(__arm__) - #define setW(x, val) do { W(x) = (val); __asm__("":::"memory"); } while (0) + #define setW(x, val) do { W(x) = (val); __asm__("":::"memory"); } while (0) #else - #define setW(x, val) (W(x) = (val)) + #define setW(x, val) (W(x) = (val)) #endif /* @@ -68,27 +67,27 @@ */ #if defined(__i386__) || defined(__x86_64__) || \ - defined(_M_IX86) || defined(_M_X64) || \ - defined(__ppc__) || defined(__ppc64__) || \ - defined(__powerpc__) || defined(__powerpc64__) || \ - defined(__s390__) || defined(__s390x__) + defined(_M_IX86) || defined(_M_X64) || \ + defined(__ppc__) || defined(__ppc64__) || \ + defined(__powerpc__) || defined(__powerpc64__) || \ + defined(__s390__) || defined(__s390x__) -#define get_be32(p) ntohl(*(unsigned int *)(p)) +#define get_be32(p) ntohl(*(const unsigned int *)(p)) #define put_be32(p, v) do { *(unsigned int *)(p) = htonl(v); } while (0) #else #define get_be32(p) ( \ - (*((unsigned char *)(p) + 0) << 24) | \ - (*((unsigned char *)(p) + 1) << 16) | \ - (*((unsigned char *)(p) + 2) << 8) | \ - (*((unsigned char *)(p) + 3) << 0) ) + (*((const unsigned char *)(p) + 0) << 24) | \ + (*((const unsigned char *)(p) + 1) << 16) | \ + (*((const unsigned char *)(p) + 2) << 8) | \ + (*((const unsigned char *)(p) + 3) << 0) ) #define put_be32(p, v) do { \ unsigned int __v = (v); \ *((unsigned char *)(p) + 0) = __v >> 24; \ *((unsigned char *)(p) + 1) = __v >> 16; \ - *((unsigned char *)(p) + 2) = __v >> 8; \ - *((unsigned char *)(p) + 3) = __v >> 0; } while (0) + *((unsigned char *)(p) + 2) = __v >> 8; \ + *((unsigned char *)(p) + 3) = __v >> 0; } while (0) #endif @@ -107,11 +106,11 @@ E += TEMP + SHA_ROL(A,5) + (fn) + (constant); \ B = SHA_ROR(B, 2); } while (0) -#define T_0_15(t, A, B, C, D, E) SHA_ROUND(t, SHA_SRC, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E ) +#define T_0_15(t, A, B, C, D, E) SHA_ROUND(t, SHA_SRC, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E ) #define T_16_19(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E ) #define T_20_39(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0x6ed9eba1, A, B, C, D, E ) #define T_40_59(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, ((B&C)+(D&(B^C))) , 0x8f1bbcdc, A, B, C, D, E ) -#define T_60_79(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0xca62c1d6, A, B, C, D, E ) +#define T_60_79(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0xca62c1d6, A, B, C, D, E ) static void blk_SHA1_Block(blk_SHA_CTX *ctx, const unsigned int *data) { @@ -233,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; @@ -243,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/block-sha1/sha1.h b/src/sha1.h index 558d6aece..93a244d76 100644 --- a/src/block-sha1/sha1.h +++ b/src/sha1.h @@ -1,9 +1,8 @@ /* - * SHA1 routine optimized to do word accesses rather than byte accesses, - * and to avoid unnecessary copies into the context array. + * Copyright (C) 2009-2012 the libgit2 contributors * - * This was initially based on the Mozilla SHA1 implementation, although - * none of the original Mozilla code remains. + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. */ typedef struct { @@ -13,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 f4a3c42cc..096da1739 100644 --- a/src/sha1_lookup.c +++ b/src/sha1_lookup.c @@ -1,27 +1,8 @@ /* - * This file is basically taken from git code. - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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 <stdio.h> @@ -33,71 +14,71 @@ * Conventional binary search loop looks like this: * * unsigned lo, hi; - * do { - * unsigned mi = (lo + hi) / 2; - * int cmp = "entry pointed at by mi" minus "target"; - * if (!cmp) - * return (mi is the wanted one) - * if (cmp > 0) - * hi = mi; "mi is larger than target" - * else - * lo = mi+1; "mi is smaller than target" - * } while (lo < hi); + * do { + * unsigned mi = (lo + hi) / 2; + * int cmp = "entry pointed at by mi" minus "target"; + * if (!cmp) + * return (mi is the wanted one) + * if (cmp > 0) + * hi = mi; "mi is larger than target" + * else + * lo = mi+1; "mi is smaller than target" + * } while (lo < hi); * * The invariants are: * * - When entering the loop, lo points at a slot that is never - * above the target (it could be at the target), hi points at a - * slot that is guaranteed to be above the target (it can never - * be at the target). + * above the target (it could be at the target), hi points at a + * slot that is guaranteed to be above the target (it can never + * be at the target). * * - We find a point 'mi' between lo and hi (mi could be the same - * as lo, but never can be as same as hi), and check if it hits - * the target. There are three cases: + * as lo, but never can be as same as hi), and check if it hits + * the target. There are three cases: * - * - if it is a hit, we are happy. + * - if it is a hit, we are happy. * - * - if it is strictly higher than the target, we set it to hi, - * and repeat the search. + * - if it is strictly higher than the target, we set it to hi, + * and repeat the search. * - * - if it is strictly lower than the target, we update lo to - * one slot after it, because we allow lo to be at the target. + * - if it is strictly lower than the target, we update lo to + * one slot after it, because we allow lo to be at the target. * - * If the loop exits, there is no matching entry. + * If the loop exits, there is no matching entry. * * When choosing 'mi', we do not have to take the "middle" but * anywhere in between lo and hi, as long as lo <= mi < hi is - * satisfied. When we somehow know that the distance between the + * satisfied. When we somehow know that the distance between the * target and lo is much shorter than the target and hi, we could * pick mi that is much closer to lo than the midway. * * Now, we can take advantage of the fact that SHA-1 is a good hash * function, and as long as there are enough entries in the table, we - * can expect uniform distribution. An entry that begins with for + * can expect uniform distribution. An entry that begins with for * example "deadbeef..." is much likely to appear much later than in - * the midway of the table. It can reasonably be expected to be near + * the midway of the table. It can reasonably be expected to be near * 87% (222/256) from the top of the table. * - * However, we do not want to pick "mi" too precisely. If the entry at + * However, we do not want to pick "mi" too precisely. If the entry at * the 87% in the above example turns out to be higher than the target * we are looking for, we would end up narrowing the search space down * only by 13%, instead of 50% we would get if we did a simple binary - * search. So we would want to hedge our bets by being less aggressive. + * search. So we would want to hedge our bets by being less aggressive. * * The table at "table" holds at least "nr" entries of "elem_size" - * bytes each. Each entry has the SHA-1 key at "key_offset". The - * table is sorted by the SHA-1 key of the entries. The caller wants + * bytes each. Each entry has the SHA-1 key at "key_offset". The + * table is sorted by the SHA-1 key of the entries. The caller wants * to find the entry with "key", and knows that the entry at "lo" is * not higher than the entry it is looking for, and that the entry at * "hi" is higher than the entry it is looking for. */ int sha1_entry_pos(const void *table, - size_t elem_size, - size_t key_offset, - unsigned lo, unsigned hi, unsigned nr, - const unsigned char *key) + size_t elem_size, + size_t key_offset, + unsigned lo, unsigned hi, unsigned nr, + const unsigned char *key) { - const unsigned char *base = table; + const unsigned char *base = (const unsigned char*)table; const unsigned char *hi_key, *lo_key; unsigned ofs_0; @@ -157,7 +138,7 @@ int sha1_entry_pos(const void *table, * end up narrowing the search space by a smaller * amount (i.e. the distance between 'mi' and 'hi') * than what we would have (i.e. about half of 'lo' - * and 'hi'). Hedge our bets to pick 'mi' less + * and 'hi'). Hedge our bets to pick 'mi' less * aggressively, i.e. make 'mi' a bit closer to the * middle than we would otherwise pick. */ @@ -173,11 +154,12 @@ int sha1_entry_pos(const void *table, #ifdef INDEX_DEBUG_LOOKUP printf("lo %u hi %u rg %u mi %u ", lo, hi, range, mi); printf("ofs %u lov %x, hiv %x, kyv %x\n", - ofs_0, lov, hiv, kyv); + ofs_0, lov, hiv, kyv); #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; @@ -192,5 +174,5 @@ int sha1_entry_pos(const void *table, lo_key = mi_key + elem_size; } } while (lo < hi); - return -lo-1; + return -((int)lo)-1; } diff --git a/src/sha1_lookup.h b/src/sha1_lookup.h index 5caa2f5ed..cd40a9d57 100644 --- a/src/sha1_lookup.h +++ b/src/sha1_lookup.h @@ -1,12 +1,18 @@ +/* + * 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_sha1_lookup_h__ #define INCLUDE_sha1_lookup_h__ #include <stdlib.h> int sha1_entry_pos(const void *table, - size_t elem_size, - size_t key_offset, - unsigned lo, unsigned hi, unsigned nr, - const unsigned char *key); + size_t elem_size, + size_t key_offset, + unsigned lo, unsigned hi, unsigned nr, + const unsigned char *key); #endif diff --git a/src/signature.c b/src/signature.c index 51a2fff47..7d329c4c9 100644 --- a/src/signature.c +++ b/src/signature.c @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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" @@ -33,48 +15,111 @@ void git_signature_free(git_signature *sig) if (sig == NULL) return; - free(sig->name); - free(sig->email); - free(sig); + git__free(sig->name); + sig->name = NULL; + git__free(sig->email); + sig->email = NULL; + git__free(sig); } -git_signature *git_signature_new(const char *name, const char *email, git_time_t time, int offset) +static const char *skip_leading_spaces(const char *buffer, const char *buffer_end) { + while (*buffer == ' ' && buffer < buffer_end) + buffer++; + + return buffer; +} + +static const char *skip_trailing_spaces(const char *buffer_start, const char *buffer_end) +{ + while (*buffer_end == ' ' && buffer_end > buffer_start) + buffer_end--; + + 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; + 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 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); + GITERR_CHECK_ALLOC(*storage); + + memcpy(*storage, left, trimmed_input_length); + (*storage)[trimmed_input_length] = 0; + + return 0; +} + +int git_signature_new(git_signature **sig_out, const char *name, const char *email, git_time_t time, int offset) +{ + int error; git_signature *p = NULL; - if ((p = git__malloc(sizeof(git_signature))) == NULL) - goto cleanup; + assert(name && email); + + *sig_out = NULL; + + p = git__calloc(1, sizeof(git_signature)); + GITERR_CHECK_ALLOC(p); + + 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->name = git__strdup(name); - p->email = git__strdup(email); p->when.time = time; p->when.offset = offset; - if (p->name == NULL || p->email == NULL) - goto cleanup; + *sig_out = p; - return p; - -cleanup: - git_signature_free(p); - return NULL; + return 0; } git_signature *git_signature_dup(const git_signature *sig) { - return git_signature_new(sig->name, sig->email, sig->when.time, sig->when.offset); + git_signature *new; + if (git_signature_new(&new, sig->name, sig->email, sig->when.time, sig->when.offset) < 0) + return NULL; + return new; } -git_signature *git_signature_now(const char *name, const char *email) +int git_signature_now(git_signature **sig_out, const char *name, const char *email) { time_t now; time_t offset; struct tm *utc_tm, *local_tm; + git_signature *sig; #ifndef GIT_WIN32 struct tm _utc, _local; #endif + *sig_out = NULL; + time(&now); /** @@ -96,163 +141,196 @@ git_signature *git_signature_now(const char *name, const char *email) if (local_tm->tm_isdst) offset += 60; - return git_signature_new(name, email, now, (int)offset); + if (git_signature_new(&sig, name, email, now, (int)offset) < 0) + return -1; + + *sig_out = sig; + + return 0; } -static int parse_timezone_offset(const char *buffer, long *offset_out) +static int timezone_error(const char *msg) { - long offset, dec_offset; - int mins, hours; + giterr_set(GITERR_INVALID, "Failed to parse TZ offset - %s", msg); + return -1; +} + +static int parse_timezone_offset(const char *buffer, int *offset_out) +{ + int dec_offset; + int mins, hours, offset; const char *offset_start; const char *offset_end; - //we are sure that *buffer == ' ' - offset_start = buffer + 1; + offset_start = buffer; if (*offset_start == '\n') { *offset_out = 0; - return GIT_SUCCESS; + return 0; } 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"); + if (git__strtol32(&dec_offset, offset_start + 1, &offset_end, 10) < 0) + 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"); + if (hours > 14) // see http://www.worldtimezone.com/faq.html + 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; if (offset_start[0] == '-') offset *= -1; - + *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 < 0) + return error; -int git_signature__parse(git_signature *sig, const char **buffer_out, - const char *buffer_end, const char *header) + *buffer_out = token_end + 1; + + if (*buffer_out > right_boundary) + return signature_error("signature is too short"); + + return 0; +} + +static const char *scan_for_previous_token(const char *buffer, const char *left_boundary) { - const size_t header_len = strlen(header); + const char *start; - int name_length, email_length; - const char *buffer = *buffer_out; - const char *line_end, *name_end, *email_end; - long offset = 0, time; + if (buffer <= left_boundary) + return NULL; - memset(sig, 0x0, sizeof(git_signature)); + start = skip_trailing_spaces(left_boundary, buffer); - line_end = memchr(buffer, '\n', buffer_end - buffer); - if (!line_end) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. No newline found");; + /* Search for previous occurence of space */ + while (start[-1] != ' ' && start > left_boundary) + start--; - if (buffer + (header_len + 1) > line_end) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Signature too short"); + return start; +} - if (memcmp(buffer, header, header_len) != 0) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Expected prefix '%s' doesn't match actual", header); +static int parse_time(git_time_t *time_out, const char *buffer) +{ + int time; + int error; - buffer += header_len; + if (*buffer == '+' || *buffer == '-') { + giterr_set(GITERR_INVALID, "Failed while parsing time. '%s' actually looks like a timezone offset.", buffer); + return -1; + } - /* Parse name */ - if ((name_end = strstr(buffer, " <")) == NULL) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Can't find e-mail start"); + error = git__strtol32(&time, buffer, &buffer, 10); - name_length = name_end - buffer; - if (name_length <= 0) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Missing tagger name"); + if (!error) + *time_out = (git_time_t)time; - sig->name = git__malloc(name_length + 1); - if (sig->name == NULL) - return GIT_ENOMEM; + return error; +} - memcpy(sig->name, buffer, name_length); - sig->name[name_length] = 0; - buffer = name_end + 2; +int git_signature__parse(git_signature *sig, const char **buffer_out, + const char *buffer_end, const char *header, char ender) +{ + const char *buffer = *buffer_out; + const char *line_end, *name_end, *email_end, *tz_start, *time_start; + int error = 0; + + memset(sig, 0x0, sizeof(git_signature)); + + if ((line_end = memchr(buffer, ender, buffer_end - buffer)) == NULL) + return signature_error("no newline given"); + + if (header) { + const size_t header_len = strlen(header); + + if (memcmp(buffer, header, header_len) != 0) + return signature_error("expected prefix doesn't match actual"); + + buffer += header_len; + } + + if (buffer > line_end) + return signature_error("signature too short"); - if (buffer >= line_end) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Ended unexpectedly"); + if ((name_end = strchr(buffer, '<')) == NULL) + return signature_error("character '<' not allowed in signature"); - /* Parse email */ - if ((email_end = strstr(buffer, "> ")) == NULL) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Can't find e-mail end"); + if ((email_end = strchr(name_end, '>')) == NULL) + return signature_error("character '>' not allowed in signature"); - email_length = email_end - buffer; - sig->email = git__malloc(email_length + 1); - if (sig->name == NULL) - return GIT_ENOMEM; + if (email_end < name_end) + return signature_error("malformed e-mail"); - memcpy(sig->email, buffer, email_length); - sig->email[email_length] = 0; - buffer = email_end + 2; + error = process_next_token(&buffer, &sig->name, name_end, line_end); + if (error < 0) + return error; - if (buffer >= line_end) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Ended unexpectedly"); + error = process_next_token(&buffer, &sig->email, email_end, line_end); + if (error < 0) + return error; - /* verify email */ - if (strpbrk(sig->email, "><\n ") != NULL) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Malformed e-mail"); + tz_start = scan_for_previous_token(line_end - 1, buffer); - if (git__strtol32(&time, buffer, &buffer, 10) < GIT_SUCCESS) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Timestamp isn't a number"); + if (tz_start == NULL) + goto clean_exit; /* No timezone nor date */ - sig->when.time = (time_t)time; + time_start = scan_for_previous_token(tz_start - 1, buffer); + 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(buffer, &offset) < GIT_SUCCESS) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse signature. Could not parse timezone offset"); - - sig->when.offset = offset; + if (parse_timezone_offset(tz_start, &sig->when.offset) < 0) { + sig->when.time = 0; /* Bogus timezone, we reset the time */ + } - *buffer_out = (line_end + 1); - return GIT_SUCCESS; +clean_exit: + *buffer_out = line_end + 1; + return 0; } -int git_signature__write(char **signature, const char *header, const git_signature *sig) +void git_signature__writebuf(git_buf *buf, const char *header, const git_signature *sig) { int offset, hours, mins; - char sig_buffer[2048]; - int sig_buffer_len; char sign; offset = sig->when.offset; sign = (sig->when.offset < 0) ? '-' : '+'; - + if (offset < 0) offset = -offset; hours = offset / 60; mins = offset % 60; - sig_buffer_len = snprintf(sig_buffer, sizeof(sig_buffer), - "%s %s <%s> %u %c%02d%02d\n", - header, sig->name, sig->email, + git_buf_printf(buf, "%s%s <%s> %u %c%02d%02d\n", + header ? header : "", sig->name, sig->email, (unsigned)sig->when.time, sign, hours, mins); - - if (sig_buffer_len < 0 || (size_t)sig_buffer_len > sizeof(sig_buffer)) - return GIT_ENOMEM; - - *signature = git__strdup(sig_buffer); - return sig_buffer_len; } - diff --git a/src/signature.h b/src/signature.h index feba6578d..97b3a055e 100644 --- a/src/signature.h +++ b/src/signature.h @@ -1,3 +1,9 @@ +/* + * 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_signature_h__ #define INCLUDE_signature_h__ @@ -6,7 +12,7 @@ #include "repository.h" #include <time.h> -int git_signature__parse(git_signature *sig, const char **buffer_out, const char *buffer_end, const char *header); -int git_signature__write(char **signature, const char *header, const git_signature *sig); +int git_signature__parse(git_signature *sig, const char **buffer_out, const char *buffer_end, const char *header, char ender); +void git_signature__writebuf(git_buf *buf, const char *header, const git_signature *sig); #endif diff --git a/src/status.c b/src/status.c new file mode 100644 index 000000000..e9ad3cfe4 --- /dev/null +++ b/src/status.c @@ -0,0 +1,242 @@ +/* + * 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 "git2.h" +#include "fileops.h" +#include "hash.h" +#include "vector.h" +#include "tree.h" +#include "git2/status.h" +#include "repository.h" +#include "ignore.h" + +#include "git2/diff.h" +#include "diff.h" + +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, + const 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); + + if ((err = git_repository_head_tree(&head, repo)) < 0) + return err; + + 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 && + (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); +} + +struct status_file_info { + unsigned int count; + unsigned int status; + char *expected; +}; + +static int get_one_status(const char *path, unsigned int status, void *data) +{ + struct status_file_info *sfi = data; + + sfi->count++; + sfi->status = status; + + if (sfi->count > 1 || strcmp(sfi->expected, path) != 0) { + giterr_set(GITERR_INVALID, + "Ambiguous path '%s' given to git_status_file", sfi->expected); + return -1; + } + + return 0; +} + +int git_status_file( + unsigned int *status_flags, + git_repository *repo, + const char *path) +{ + int error; + git_status_options opts; + struct status_file_info sfi; + + assert(status_flags && repo && path); + + memset(&sfi, 0, sizeof(sfi)); + if ((sfi.expected = git__strdup(path)) == NULL) + return -1; + + 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 | + GIT_STATUS_OPT_INCLUDE_UNMODIFIED; + opts.pathspec.count = 1; + opts.pathspec.strings = &sfi.expected; + + error = git_status_foreach_ext(repo, &opts, get_one_status, &sfi); + + if (!error && !sfi.count) { + giterr_set(GITERR_INVALID, + "Attempt to get status of nonexistent file '%s'", path); + error = GIT_ENOTFOUND; + } + + *status_flags = sfi.status; + + git__free(sfi.expected); + + return error; +} + +int git_status_should_ignore( + int *ignored, + git_repository *repo, + const char *path) +{ + int error; + git_ignores ignores; + + 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..da5ca0dba --- /dev/null +++ b/src/strmap.h @@ -0,0 +1,64 @@ +/* + * 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, rval) do { \ + khiter_t __pos = kh_put(str, h, key, &rval); \ + if (rval >= 0) { \ + if (rval == 0) kh_key(h, __pos) = key; \ + kh_val(h, __pos) = val; \ + } } while (0) + +#define git_strmap_insert2(h, key, val, oldv, rval) do { \ + khiter_t __pos = kh_put(str, h, key, &rval); \ + if (rval >= 0) { \ + if (rval == 0) { \ + oldv = kh_val(h, __pos); \ + kh_key(h, __pos) = key; \ + } else { oldv = NULL; } \ + kh_val(h, __pos) = val; \ + } } while (0) + +#define git_strmap_delete(h, key) do { \ + khiter_t __pos = git_strmap_lookup_index(h, key); \ + if (git_strmap_valid_index(h, __pos)) \ + git_strmap_delete_at(h, __pos); } 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..3c07e657d --- /dev/null +++ b/src/submodule.c @@ -0,0 +1,387 @@ +/* + * 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__parse_bool(&sm->fetch_recurse, value) < 0) { + giterr_set(GITERR_INVALID, + "Invalid value for submodule 'fetchRecurseSubmodules' property: '%s'", value); + 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; +} diff --git a/src/t03-data.h b/src/t03-data.h deleted file mode 100644 index a4b73fec3..000000000 --- a/src/t03-data.h +++ /dev/null @@ -1,344 +0,0 @@ - -typedef struct object_data { - char *id; /* object id (sha1) */ - char *dir; /* object store (fan-out) directory name */ - char *file; /* object store filename */ -} object_data; - -static object_data commit = { - "3d7f8a6af076c8c3f20071a8935cdbe8228594d1", - "test-objects/3d", - "test-objects/3d/7f8a6af076c8c3f20071a8935cdbe8228594d1", -}; - -static unsigned char commit_data[] = { - 0x74, 0x72, 0x65, 0x65, 0x20, 0x64, 0x66, 0x66, - 0x32, 0x64, 0x61, 0x39, 0x30, 0x62, 0x32, 0x35, - 0x34, 0x65, 0x31, 0x62, 0x65, 0x62, 0x38, 0x38, - 0x39, 0x64, 0x31, 0x66, 0x31, 0x66, 0x31, 0x32, - 0x38, 0x38, 0x62, 0x65, 0x31, 0x38, 0x30, 0x33, - 0x37, 0x38, 0x32, 0x64, 0x66, 0x0a, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x20, 0x41, 0x20, 0x55, - 0x20, 0x54, 0x68, 0x6f, 0x72, 0x20, 0x3c, 0x61, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x40, 0x65, 0x78, - 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, - 0x6d, 0x3e, 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, - 0x31, 0x34, 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, - 0x30, 0x30, 0x30, 0x0a, 0x63, 0x6f, 0x6d, 0x6d, - 0x69, 0x74, 0x74, 0x65, 0x72, 0x20, 0x43, 0x20, - 0x4f, 0x20, 0x4d, 0x69, 0x74, 0x74, 0x65, 0x72, - 0x20, 0x3c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x74, 0x65, 0x72, 0x40, 0x65, 0x78, 0x61, 0x6d, - 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x3e, - 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, 0x31, 0x34, - 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, 0x30, 0x30, - 0x30, 0x0a, 0x0a, 0x41, 0x20, 0x6f, 0x6e, 0x65, - 0x2d, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x63, 0x6f, - 0x6d, 0x6d, 0x69, 0x74, 0x20, 0x73, 0x75, 0x6d, - 0x6d, 0x61, 0x72, 0x79, 0x0a, 0x0a, 0x54, 0x68, - 0x65, 0x20, 0x62, 0x6f, 0x64, 0x79, 0x20, 0x6f, - 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, - 0x6d, 0x6d, 0x69, 0x74, 0x20, 0x6d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x2c, 0x20, 0x63, 0x6f, - 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, - 0x20, 0x66, 0x75, 0x72, 0x74, 0x68, 0x65, 0x72, - 0x20, 0x65, 0x78, 0x70, 0x6c, 0x61, 0x6e, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x6f, 0x66, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x70, 0x75, 0x72, 0x70, - 0x6f, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x73, 0x20, 0x69, 0x6e, 0x74, 0x72, 0x6f, - 0x64, 0x75, 0x63, 0x65, 0x64, 0x20, 0x62, 0x79, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x2e, 0x0a, 0x0a, 0x53, 0x69, - 0x67, 0x6e, 0x65, 0x64, 0x2d, 0x6f, 0x66, 0x2d, - 0x62, 0x79, 0x3a, 0x20, 0x41, 0x20, 0x55, 0x20, - 0x54, 0x68, 0x6f, 0x72, 0x20, 0x3c, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x40, 0x65, 0x78, 0x61, - 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, - 0x3e, 0x0a, -}; - -static git_rawobj commit_obj = { - commit_data, - sizeof(commit_data), - GIT_OBJ_COMMIT -}; - -static object_data tree = { - "dff2da90b254e1beb889d1f1f1288be1803782df", - "test-objects/df", - "test-objects/df/f2da90b254e1beb889d1f1f1288be1803782df", -}; - -static unsigned char tree_data[] = { - 0x31, 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x6f, - 0x6e, 0x65, 0x00, 0x8b, 0x13, 0x78, 0x91, 0x79, - 0x1f, 0xe9, 0x69, 0x27, 0xad, 0x78, 0xe6, 0x4b, - 0x0a, 0xad, 0x7b, 0xde, 0xd0, 0x8b, 0xdc, 0x31, - 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x73, 0x6f, - 0x6d, 0x65, 0x00, 0xfd, 0x84, 0x30, 0xbc, 0x86, - 0x4c, 0xfc, 0xd5, 0xf1, 0x0e, 0x55, 0x90, 0xf8, - 0xa4, 0x47, 0xe0, 0x1b, 0x94, 0x2b, 0xfe, 0x31, - 0x30, 0x30, 0x36, 0x34, 0x34, 0x20, 0x74, 0x77, - 0x6f, 0x00, 0x78, 0x98, 0x19, 0x22, 0x61, 0x3b, - 0x2a, 0xfb, 0x60, 0x25, 0x04, 0x2f, 0xf6, 0xbd, - 0x87, 0x8a, 0xc1, 0x99, 0x4e, 0x85, 0x31, 0x30, - 0x30, 0x36, 0x34, 0x34, 0x20, 0x7a, 0x65, 0x72, - 0x6f, 0x00, 0xe6, 0x9d, 0xe2, 0x9b, 0xb2, 0xd1, - 0xd6, 0x43, 0x4b, 0x8b, 0x29, 0xae, 0x77, 0x5a, - 0xd8, 0xc2, 0xe4, 0x8c, 0x53, 0x91, -}; - -static git_rawobj tree_obj = { - tree_data, - sizeof(tree_data), - GIT_OBJ_TREE -}; - -static object_data tag = { - "09d373e1dfdc16b129ceec6dd649739911541e05", - "test-objects/09", - "test-objects/09/d373e1dfdc16b129ceec6dd649739911541e05", -}; - -static unsigned char tag_data[] = { - 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x33, - 0x64, 0x37, 0x66, 0x38, 0x61, 0x36, 0x61, 0x66, - 0x30, 0x37, 0x36, 0x63, 0x38, 0x63, 0x33, 0x66, - 0x32, 0x30, 0x30, 0x37, 0x31, 0x61, 0x38, 0x39, - 0x33, 0x35, 0x63, 0x64, 0x62, 0x65, 0x38, 0x32, - 0x32, 0x38, 0x35, 0x39, 0x34, 0x64, 0x31, 0x0a, - 0x74, 0x79, 0x70, 0x65, 0x20, 0x63, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x0a, 0x74, 0x61, 0x67, 0x20, - 0x76, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x0a, 0x74, - 0x61, 0x67, 0x67, 0x65, 0x72, 0x20, 0x43, 0x20, - 0x4f, 0x20, 0x4d, 0x69, 0x74, 0x74, 0x65, 0x72, - 0x20, 0x3c, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x74, 0x65, 0x72, 0x40, 0x65, 0x78, 0x61, 0x6d, - 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x3e, - 0x20, 0x31, 0x32, 0x32, 0x37, 0x38, 0x31, 0x34, - 0x32, 0x39, 0x37, 0x20, 0x2b, 0x30, 0x30, 0x30, - 0x30, 0x0a, 0x0a, 0x54, 0x68, 0x69, 0x73, 0x20, - 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, - 0x61, 0x67, 0x20, 0x6f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x72, 0x65, - 0x6c, 0x65, 0x61, 0x73, 0x65, 0x20, 0x76, 0x30, - 0x2e, 0x30, 0x2e, 0x31, 0x0a, -}; - -static git_rawobj tag_obj = { - tag_data, - sizeof(tag_data), - GIT_OBJ_TAG -}; - -static object_data zero = { - "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", - "test-objects/e6", - "test-objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391", -}; - -static unsigned char zero_data[] = { - 0x00 /* dummy data */ -}; - -static git_rawobj zero_obj = { - zero_data, - 0, - GIT_OBJ_BLOB -}; - -static object_data one = { - "8b137891791fe96927ad78e64b0aad7bded08bdc", - "test-objects/8b", - "test-objects/8b/137891791fe96927ad78e64b0aad7bded08bdc", -}; - -static unsigned char one_data[] = { - 0x0a, -}; - -static git_rawobj one_obj = { - one_data, - sizeof(one_data), - GIT_OBJ_BLOB -}; - -static object_data two = { - "78981922613b2afb6025042ff6bd878ac1994e85", - "test-objects/78", - "test-objects/78/981922613b2afb6025042ff6bd878ac1994e85", -}; - -static unsigned char two_data[] = { - 0x61, 0x0a, -}; - -static git_rawobj two_obj = { - two_data, - sizeof(two_data), - GIT_OBJ_BLOB -}; - -static object_data some = { - "fd8430bc864cfcd5f10e5590f8a447e01b942bfe", - "test-objects/fd", - "test-objects/fd/8430bc864cfcd5f10e5590f8a447e01b942bfe", -}; - -static unsigned char some_data[] = { - 0x2f, 0x2a, 0x0a, 0x20, 0x2a, 0x20, 0x54, 0x68, - 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, - 0x69, 0x73, 0x20, 0x66, 0x72, 0x65, 0x65, 0x20, - 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, - 0x3b, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x63, 0x61, - 0x6e, 0x20, 0x72, 0x65, 0x64, 0x69, 0x73, 0x74, - 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x69, - 0x74, 0x20, 0x61, 0x6e, 0x64, 0x2f, 0x6f, 0x72, - 0x20, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x0a, - 0x20, 0x2a, 0x20, 0x69, 0x74, 0x20, 0x75, 0x6e, - 0x64, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x74, 0x65, 0x72, 0x6d, 0x73, 0x20, 0x6f, 0x66, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x4e, 0x55, - 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, - 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, - 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x2c, - 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x20, 0x32, 0x2c, 0x0a, 0x20, 0x2a, 0x20, 0x61, - 0x73, 0x20, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, - 0x68, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x46, 0x72, 0x65, 0x65, 0x20, - 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, - 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x2e, 0x0a, 0x20, 0x2a, 0x0a, - 0x20, 0x2a, 0x20, 0x49, 0x6e, 0x20, 0x61, 0x64, - 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, - 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x47, 0x4e, 0x55, 0x20, 0x47, 0x65, 0x6e, - 0x65, 0x72, 0x61, 0x6c, 0x20, 0x50, 0x75, 0x62, - 0x6c, 0x69, 0x63, 0x20, 0x4c, 0x69, 0x63, 0x65, - 0x6e, 0x73, 0x65, 0x2c, 0x0a, 0x20, 0x2a, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x61, 0x75, 0x74, 0x68, - 0x6f, 0x72, 0x73, 0x20, 0x67, 0x69, 0x76, 0x65, - 0x20, 0x79, 0x6f, 0x75, 0x20, 0x75, 0x6e, 0x6c, - 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x20, 0x70, - 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x6c, 0x69, 0x6e, - 0x6b, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, - 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, 0x0a, 0x20, - 0x2a, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x69, - 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x69, - 0x6e, 0x74, 0x6f, 0x20, 0x63, 0x6f, 0x6d, 0x62, - 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x74, - 0x68, 0x65, 0x72, 0x20, 0x70, 0x72, 0x6f, 0x67, - 0x72, 0x61, 0x6d, 0x73, 0x2c, 0x0a, 0x20, 0x2a, - 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x6f, 0x20, - 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, - 0x74, 0x65, 0x20, 0x74, 0x68, 0x6f, 0x73, 0x65, - 0x20, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x77, 0x69, - 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x61, 0x6e, - 0x79, 0x20, 0x72, 0x65, 0x73, 0x74, 0x72, 0x69, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x2a, - 0x20, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x20, - 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, - 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, - 0x65, 0x2e, 0x20, 0x20, 0x28, 0x54, 0x68, 0x65, - 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, - 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, - 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a, - 0x20, 0x2a, 0x20, 0x72, 0x65, 0x73, 0x74, 0x72, - 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, - 0x64, 0x6f, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x79, - 0x20, 0x69, 0x6e, 0x20, 0x6f, 0x74, 0x68, 0x65, - 0x72, 0x20, 0x72, 0x65, 0x73, 0x70, 0x65, 0x63, - 0x74, 0x73, 0x3b, 0x20, 0x66, 0x6f, 0x72, 0x20, - 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2c, - 0x20, 0x74, 0x68, 0x65, 0x79, 0x20, 0x63, 0x6f, - 0x76, 0x65, 0x72, 0x0a, 0x20, 0x2a, 0x20, 0x6d, - 0x6f, 0x64, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2c, - 0x20, 0x61, 0x6e, 0x64, 0x20, 0x64, 0x69, 0x73, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, - 0x6e, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x6e, - 0x6f, 0x74, 0x20, 0x6c, 0x69, 0x6e, 0x6b, 0x65, - 0x64, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x0a, 0x20, - 0x2a, 0x20, 0x61, 0x20, 0x63, 0x6f, 0x6d, 0x62, - 0x69, 0x6e, 0x65, 0x64, 0x20, 0x65, 0x78, 0x65, - 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x2e, - 0x29, 0x0a, 0x20, 0x2a, 0x0a, 0x20, 0x2a, 0x20, - 0x54, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, - 0x65, 0x20, 0x69, 0x73, 0x20, 0x64, 0x69, 0x73, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x64, - 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x68, 0x6f, 0x70, 0x65, 0x20, 0x74, 0x68, 0x61, - 0x74, 0x20, 0x69, 0x74, 0x20, 0x77, 0x69, 0x6c, - 0x6c, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, - 0x66, 0x75, 0x6c, 0x2c, 0x20, 0x62, 0x75, 0x74, - 0x0a, 0x20, 0x2a, 0x20, 0x57, 0x49, 0x54, 0x48, - 0x4f, 0x55, 0x54, 0x20, 0x41, 0x4e, 0x59, 0x20, - 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x59, - 0x3b, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, - 0x74, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x69, - 0x65, 0x64, 0x20, 0x77, 0x61, 0x72, 0x72, 0x61, - 0x6e, 0x74, 0x79, 0x20, 0x6f, 0x66, 0x0a, 0x20, - 0x2a, 0x20, 0x4d, 0x45, 0x52, 0x43, 0x48, 0x41, - 0x4e, 0x54, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, - 0x59, 0x20, 0x6f, 0x72, 0x20, 0x46, 0x49, 0x54, - 0x4e, 0x45, 0x53, 0x53, 0x20, 0x46, 0x4f, 0x52, - 0x20, 0x41, 0x20, 0x50, 0x41, 0x52, 0x54, 0x49, - 0x43, 0x55, 0x4c, 0x41, 0x52, 0x20, 0x50, 0x55, - 0x52, 0x50, 0x4f, 0x53, 0x45, 0x2e, 0x20, 0x20, - 0x53, 0x65, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x47, 0x4e, 0x55, 0x0a, 0x20, 0x2a, 0x20, 0x47, - 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x20, 0x50, - 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x4c, 0x69, - 0x63, 0x65, 0x6e, 0x73, 0x65, 0x20, 0x66, 0x6f, - 0x72, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x64, - 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x2e, 0x0a, - 0x20, 0x2a, 0x0a, 0x20, 0x2a, 0x20, 0x59, 0x6f, - 0x75, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, - 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x72, 0x65, - 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x20, 0x61, - 0x20, 0x63, 0x6f, 0x70, 0x79, 0x20, 0x6f, 0x66, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x4e, 0x55, - 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, - 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, - 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x0a, - 0x20, 0x2a, 0x20, 0x61, 0x6c, 0x6f, 0x6e, 0x67, - 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, - 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, - 0x61, 0x6d, 0x3b, 0x20, 0x73, 0x65, 0x65, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, - 0x20, 0x43, 0x4f, 0x50, 0x59, 0x49, 0x4e, 0x47, - 0x2e, 0x20, 0x20, 0x49, 0x66, 0x20, 0x6e, 0x6f, - 0x74, 0x2c, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, - 0x20, 0x74, 0x6f, 0x0a, 0x20, 0x2a, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x46, 0x72, 0x65, 0x65, 0x20, - 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, - 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x35, 0x31, 0x20, - 0x46, 0x72, 0x61, 0x6e, 0x6b, 0x6c, 0x69, 0x6e, - 0x20, 0x53, 0x74, 0x72, 0x65, 0x65, 0x74, 0x2c, - 0x20, 0x46, 0x69, 0x66, 0x74, 0x68, 0x20, 0x46, - 0x6c, 0x6f, 0x6f, 0x72, 0x2c, 0x0a, 0x20, 0x2a, - 0x20, 0x42, 0x6f, 0x73, 0x74, 0x6f, 0x6e, 0x2c, - 0x20, 0x4d, 0x41, 0x20, 0x30, 0x32, 0x31, 0x31, - 0x30, 0x2d, 0x31, 0x33, 0x30, 0x31, 0x2c, 0x20, - 0x55, 0x53, 0x41, 0x2e, 0x0a, 0x20, 0x2a, 0x2f, - 0x0a, -}; - -static git_rawobj some_obj = { - some_data, - sizeof(some_data), - GIT_OBJ_BLOB -}; @@ -1,32 +1,15 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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 "tag.h" #include "signature.h" +#include "message.h" #include "git2/object.h" #include "git2/repository.h" #include "git2/signature.h" @@ -34,9 +17,9 @@ void git_tag__free(git_tag *tag) { git_signature_free(tag->tagger); - free(tag->message); - free(tag->tag_name); - free(tag); + git__free(tag->message); + git__free(tag->tag_name); + git__free(tag); } const git_oid *git_tag_id(git_tag *c) @@ -79,24 +62,32 @@ const char *git_tag_message(git_tag *t) return t->message; } -static int parse_tag_buffer(git_tag *tag, const char *buffer, const char *buffer_end) +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; - if ((error = git__parse_oid(&tag->target, &buffer, buffer_end, "object ")) < 0) - return git__rethrow(error, "Failed to parse tag. Object field invalid"); + const char *buffer_end = buffer + length; + + 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; @@ -105,7 +96,7 @@ static int parse_tag_buffer(git_tag *tag, const char *buffer, const char *buffer 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; @@ -115,262 +106,289 @@ static int parse_tag_buffer(git_tag *tag, const char *buffer, const char *buffer } 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'; buffer = search + 1; - tag->tagger = git__malloc(sizeof(git_signature)); - if (tag->tagger == NULL) - return GIT_ENOMEM; + tag->tagger = NULL; + if (*buffer != '\n') { + tag->tagger = git__malloc(sizeof(git_signature)); + GITERR_CHECK_ALLOC(tag->tagger); - if ((error = git_signature__parse(tag->tagger, &buffer, buffer_end, "tagger ")) != 0) { - free(tag->tag_name); - git_signature_free(tag->tagger); - 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 retreive_tag_reference(git_reference **tag_reference_out, char *ref_name_out, git_repository *repo, const char *tag_name) +static int retrieve_tag_reference( + git_reference **tag_reference_out, + git_buf *ref_name_out, + git_repository *repo, + const char *tag_name) { git_reference *tag_ref; int error; - git__joinpath(ref_name_out, GIT_REFS_TAGS_DIR, tag_name); - error = git_reference_lookup(&tag_ref, repo, ref_name_out); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to retrieve tag reference"); + *tag_reference_out = NULL; + + 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 < 0) + 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 tag_create( +static int write_tag_annotation( git_oid *oid, git_repository *repo, const char *tag_name, - const git_oid *target, - git_otype target_type, + const git_object *target, const git_signature *tagger, - const char *message, - int allow_ref_overwrite) + const char *message) { - size_t final_size = 0; - git_odb_stream *stream; - - const char *type_str; - char *tagger_str; - git_reference *new_ref; - - char ref_name[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH]; - - int type_str_len, tag_name_len, tagger_str_len, message_len; - int error, should_update_ref = 0; - - /** Ensure the tag name doesn't conflict with an already existing - reference unless overwriting has explictly been requested **/ - error = retreive_tag_reference(&new_ref, ref_name, repo, tag_name); - - switch (error) { - case GIT_SUCCESS: - if (!allow_ref_overwrite) - return GIT_EEXISTS; - should_update_ref = 1; - - /* Fall trough */ - - case GIT_ENOTFOUND: - break; - - default: - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create tag"); - } - - if (!git_odb_exists(repo->db, target)) - return git__throw(GIT_ENOTFOUND, "Failed to create tag. Object to tag doesn't exist"); - - /* Try to find out what the type is */ - if (target_type == GIT_OBJ_ANY) { - size_t _unused; - error = git_odb_read_header(&_unused, &target_type, repo->db, target); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to create tag"); - } - - type_str = git_object_type2string(target_type); - - tagger_str_len = git_signature__write(&tagger_str, "tagger", tagger); + git_buf tag = GIT_BUF_INIT, cleaned_message = GIT_BUF_INIT; + git_odb *odb; - type_str_len = strlen(type_str); - tag_name_len = strlen(tag_name); - message_len = strlen(message); + git_oid__writebuf(&tag, "object ", git_object_id(target)); + git_buf_printf(&tag, "type %s\n", git_object_type2string(git_object_type(target))); + git_buf_printf(&tag, "tag %s\n", tag_name); + git_signature__writebuf(&tag, "tagger ", tagger); + git_buf_putc(&tag, '\n'); - final_size += GIT_OID_LINE_LENGTH("object"); - final_size += STRLEN("type ") + type_str_len + 1; - final_size += STRLEN("tag ") + tag_name_len + 1; - final_size += tagger_str_len; - final_size += 1 + message_len; + /* Remove comments by default */ + if (git_message_prettify(&cleaned_message, message, 1) < 0) + goto on_error; - if ((error = git_odb_open_wstream(&stream, repo->db, final_size, GIT_OBJ_TAG)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to create tag"); + if (git_buf_puts(&tag, git_buf_cstr(&cleaned_message)) < 0) + goto on_error; - git__write_oid(stream, "object", target); + git_buf_free(&cleaned_message); - stream->write(stream, "type ", STRLEN("type ")); - stream->write(stream, type_str, type_str_len); + if (git_repository_odb__weakptr(&odb, repo) < 0) + goto on_error; - stream->write(stream, "\ntag ", STRLEN("\ntag ")); - stream->write(stream, tag_name, tag_name_len); - stream->write(stream, "\n", 1); + if (git_odb_write(oid, odb, tag.ptr, tag.size, GIT_OBJ_TAG) < 0) + goto on_error; - stream->write(stream, tagger_str, tagger_str_len); - free(tagger_str); + git_buf_free(&tag); + return 0; - stream->write(stream, "\n", 1); - stream->write(stream, message, message_len); - - - error = stream->finalize_write(oid, stream); - stream->free(stream); - - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to create tag"); - - if (!should_update_ref) - error = git_reference_create_oid(&new_ref, repo, ref_name, oid); - else - error = git_reference_set_oid(new_ref, oid); - - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create tag"); +on_error: + git_buf_free(&tag); + git_buf_free(&cleaned_message); + giterr_set(GITERR_OBJECT, "Failed to create tag annotation."); + return -1; } -int git_tag_create_frombuffer(git_oid *oid, git_repository *repo, const char *buffer) +static int git_tag_create__internal( + git_oid *oid, + git_repository *repo, + const char *tag_name, + const git_object *target, + const git_signature *tagger, + const char *message, + int allow_ref_overwrite, + int create_tag_annotation) { - git_tag tag; + git_reference *new_ref = NULL; + git_buf ref_name = GIT_BUF_INIT; + int error; - assert(oid && buffer); + assert(repo && tag_name && target); + assert(!create_tag_annotation || (tagger && message)); - memset(&tag, 0, sizeof(tag)); + if (git_object_owner(target) != repo) { + giterr_set(GITERR_INVALID, "The given target does not belong to this repository"); + return -1; + } - if ((error = parse_tag_buffer(&tag, buffer, buffer + strlen(buffer))) < GIT_SUCCESS) - return git__rethrow(error, "Failed to create tag"); + error = retrieve_tag_reference_oid(oid, &ref_name, repo, tag_name); + if (error < 0 && error != GIT_ENOTFOUND) + return -1; - error = git_tag_create(oid, repo, tag.tag_name, &tag.target, tag.type, tag.tagger, tag.message); + /** Ensure the tag name doesn't conflict with an already existing + * reference unless overwriting has explictly been requested **/ + if (error == 0 && !allow_ref_overwrite) { + git_buf_free(&ref_name); + giterr_set(GITERR_TAG, "Tag already exists"); + return GIT_EEXISTS; + } - git_signature_free(tag.tagger); - free(tag.tag_name); - free(tag.message); + if (create_tag_annotation) { + if (write_tag_annotation(oid, repo, tag_name, target, tagger, message) < 0) + return -1; + } else + git_oid_cpy(oid, git_object_id(target)); - return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create tag"); -} + error = git_reference_create_oid(&new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite); -int git_tag_create_o( - git_oid *oid, - git_repository *repo, - const char *tag_name, - const git_object *target, - const git_signature *tagger, - const char *message) -{ - return tag_create( - oid, repo, tag_name, - git_object_id(target), - git_object_type(target), - tagger, message, 0); + git_reference_free(new_ref); + git_buf_free(&ref_name); + return error; } int git_tag_create( git_oid *oid, git_repository *repo, const char *tag_name, - const git_oid *target, - git_otype target_type, + const git_object *target, const git_signature *tagger, - const char *message) + const char *message, + int allow_ref_overwrite) { - return tag_create( - oid, repo, tag_name, - target, - target_type, - tagger, message, 0); + return git_tag_create__internal(oid, repo, tag_name, target, tagger, message, allow_ref_overwrite, 1); } -int git_tag_create_fo( +int git_tag_create_lightweight( git_oid *oid, git_repository *repo, const char *tag_name, const git_object *target, - const git_signature *tagger, - const char *message) + int allow_ref_overwrite) { - return tag_create( - oid, repo, tag_name, - git_object_id(target), - git_object_type(target), - tagger, message, 1); + return git_tag_create__internal(oid, repo, tag_name, target, NULL, NULL, allow_ref_overwrite, 0); } -int git_tag_create_f( - git_oid *oid, - git_repository *repo, - const char *tag_name, - const git_oid *target, - git_otype target_type, - const git_signature *tagger, - const char *message) +int git_tag_create_frombuffer(git_oid *oid, git_repository *repo, const char *buffer, int allow_ref_overwrite) { - return tag_create( - oid, repo, tag_name, - target, - target_type, - tagger, message, 1); + git_tag tag; + int error; + git_odb *odb; + git_odb_stream *stream; + git_odb_object *target_obj; + + git_reference *new_ref = NULL; + git_buf ref_name = GIT_BUF_INIT; + + assert(oid && buffer); + + memset(&tag, 0, sizeof(tag)); + + if (git_repository_odb__weakptr(&odb, repo) < 0) + return -1; + + /* validate the buffer */ + if (git_tag__parse_buffer(&tag, buffer, strlen(buffer)) < 0) + return -1; + + /* validate the target */ + if (git_odb_read(&target_obj, odb, &tag.target) < 0) + goto on_error; + + if (tag.type != target_obj->raw.type) { + giterr_set(GITERR_TAG, "The type for the given target is invalid"); + goto on_error; + } + + error = retrieve_tag_reference_oid(oid, &ref_name, repo, tag.tag_name); + if (error < 0 && error != GIT_ENOTFOUND) + 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 (error == 0 && !allow_ref_overwrite) { + giterr_set(GITERR_TAG, "Tag already exists"); + return GIT_EEXISTS; + } + + /* write the buffer */ + 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 < 0) { + git_buf_free(&ref_name); + return -1; + } + + error = git_reference_create_oid(&new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite); + + git_reference_free(new_ref); + git_buf_free(&ref_name); + + 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) { int error; git_reference *tag_ref; - char ref_name[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH]; + git_buf ref_name = GIT_BUF_INIT; - error = retreive_tag_reference(&tag_ref, ref_name, repo, tag_name); - if (error < GIT_SUCCESS) - return git__rethrow(error, "Failed to delete tag"); + error = retrieve_tag_reference(&tag_ref, &ref_name, repo, tag_name); + + git_buf_free(&ref_name); + + if (error < 0) + return -1; return git_reference_delete(tag_ref); } @@ -378,32 +396,76 @@ int git_tag_delete(git_repository *repo, const char *tag_name) int git_tag__parse(git_tag *tag, git_odb_object *obj) { assert(tag); - return parse_tag_buffer(tag, obj->raw.data, (char *)obj->raw.data + obj->raw.len); + return git_tag__parse_buffer(tag, obj->raw.data, obj->raw.len); } +typedef struct { + git_vector *taglist; + const char *pattern; +} tag_filter_data; + +#define GIT_REFS_TAGS_DIR_LEN strlen(GIT_REFS_TAGS_DIR) + static int tag_list_cb(const char *tag_name, void *payload) { - if (git__prefixcmp(tag_name, GIT_REFS_TAGS_DIR) == 0) - return git_vector_insert((git_vector *)payload, git__strdup(tag_name)); + tag_filter_data *filter; - return GIT_SUCCESS; + if (git__prefixcmp(tag_name, GIT_REFS_TAGS_DIR) != 0) + return 0; + + filter = (tag_filter_data *)payload; + if (!*filter->pattern || p_fnmatch(filter->pattern, tag_name + GIT_REFS_TAGS_DIR_LEN, 0) == 0) + return git_vector_insert(filter->taglist, git__strdup(tag_name)); + + return 0; } -int git_tag_list(git_strarray *tag_names, git_repository *repo) +int git_tag_list_match(git_strarray *tag_names, const char *pattern, git_repository *repo) { int error; + tag_filter_data filter; git_vector taglist; - if (git_vector_init(&taglist, 8, NULL) < GIT_SUCCESS) - return GIT_ENOMEM; + assert(tag_names && repo && pattern); - error = git_reference_listcb(repo, GIT_REF_OID|GIT_REF_PACKED, &tag_list_cb, (void *)&taglist); - if (error < GIT_SUCCESS) { + if (git_vector_init(&taglist, 8, NULL) < 0) + return -1; + + filter.taglist = &taglist; + filter.pattern = pattern; + + error = git_reference_foreach(repo, GIT_REF_OID|GIT_REF_PACKED, &tag_list_cb, (void *)&filter); + 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; } @@ -1,3 +1,9 @@ +/* + * 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_tag_h__ #define INCLUDE_tag_h__ @@ -18,5 +24,6 @@ struct git_tag { void git_tag__free(git_tag *tag); int git_tag__parse(git_tag *tag, git_odb_object *obj); +int git_tag__parse_buffer(git_tag *tag, const char *data, size_t len); #endif diff --git a/src/thread-utils.c b/src/thread-utils.c index 5e8220f46..0ca01ef82 100644 --- a/src/thread-utils.c +++ b/src/thread-utils.c @@ -1,11 +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. + */ #include "common.h" #include "thread-utils.h" #ifdef _WIN32 -# define WIN32_LEAN_AND_MEAN -# include <windows.h> +# define WIN32_LEAN_AND_MEAN +# include <windows.h> #elif defined(hpux) || defined(__hpux) || defined(_hpux) -# include <sys/pstat.h> +# include <sys/pstat.h> #endif /* @@ -14,11 +20,11 @@ * with this disgusting nest of #ifdefs. */ #ifndef _SC_NPROCESSORS_ONLN -# ifdef _SC_NPROC_ONLN -# define _SC_NPROCESSORS_ONLN _SC_NPROC_ONLN -# elif defined _SC_CRAY_NCPU -# define _SC_NPROCESSORS_ONLN _SC_CRAY_NCPU -# endif +# ifdef _SC_NPROC_ONLN +# define _SC_NPROCESSORS_ONLN _SC_NPROC_ONLN +# elif defined _SC_CRAY_NCPU +# define _SC_NPROCESSORS_ONLN _SC_CRAY_NCPU +# endif #endif int git_online_cpus(void) diff --git a/src/thread-utils.h b/src/thread-utils.h index 20d6022ea..a309e93d1 100644 --- a/src/thread-utils.h +++ b/src/thread-utils.h @@ -1,11 +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_thread_utils_h__ #define INCLUDE_thread_utils_h__ - +#include "common.h" /* Common operations even if threading has been disabled */ typedef struct { -#if defined(_MSC_VER) +#if defined(GIT_WIN32) volatile long val; #else volatile int val; @@ -27,10 +33,10 @@ GIT_INLINE(void) git_atomic_set(git_atomic *a, int val) /* Pthreads Mutex */ #define git_mutex pthread_mutex_t -#define git_mutex_init(a) pthread_mutex_init(a, NULL) -#define git_mutex_lock(a) pthread_mutex_lock(a) +#define git_mutex_init(a) pthread_mutex_init(a, NULL) +#define git_mutex_lock(a) pthread_mutex_lock(a) #define git_mutex_unlock(a) pthread_mutex_unlock(a) -#define git_mutex_free(a) pthread_mutex_destroy(a) +#define git_mutex_free(a) pthread_mutex_destroy(a) /* Pthreads condition vars -- disabled by now */ #define git_cond unsigned int //pthread_cond_t @@ -42,10 +48,10 @@ GIT_INLINE(void) git_atomic_set(git_atomic *a, int val) GIT_INLINE(int) git_atomic_inc(git_atomic *a) { -#ifdef __GNUC__ - return __sync_add_and_fetch(&a->val, 1); -#elif defined(_MSC_VER) +#if defined(GIT_WIN32) return InterlockedIncrement(&a->val); +#elif defined(__GNUC__) + return __sync_add_and_fetch(&a->val, 1); #else # error "Unsupported architecture for atomic operations" #endif @@ -53,10 +59,10 @@ GIT_INLINE(int) git_atomic_inc(git_atomic *a) GIT_INLINE(int) git_atomic_dec(git_atomic *a) { -#ifdef __GNUC__ - return __sync_sub_and_fetch(&a->val, 1); -#elif defined(_MSC_VER) +#if defined(GIT_WIN32) return InterlockedDecrement(&a->val); +#elif defined(__GNUC__) + return __sync_sub_and_fetch(&a->val, 1); #else # error "Unsupported architecture for atomic operations" #endif diff --git a/src/transport.c b/src/transport.c new file mode 100644 index 000000000..fb2b94946 --- /dev/null +++ b/src/transport.c @@ -0,0 +1,97 @@ +/* + * 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 "git2/types.h" +#include "git2/remote.h" +#include "git2/net.h" +#include "transport.h" +#include "path.h" + +static struct { + char *prefix; + git_transport_cb fn; +} transports[] = { + {"git://", git_transport_git}, + {"http://", git_transport_http}, + {"https://", git_transport_https}, + {"file://", git_transport_local}, + {"git+ssh://", git_transport_dummy}, + {"ssh+git://", git_transport_dummy}, + {NULL, 0} +}; + +#define GIT_TRANSPORT_COUNT (sizeof(transports)/sizeof(transports[0])) - 1 + +static git_transport_cb transport_find_fn(const char *url) +{ + size_t i = 0; + + // First, check to see if it's an obvious URL, which a URL scheme + for (i = 0; i < GIT_TRANSPORT_COUNT; ++i) { + if (!strncasecmp(url, transports[i].prefix, strlen(transports[i].prefix))) + return transports[i].fn; + } + + /* still here? Check to see if the path points to a file on the local file system */ + if ((git_path_exists(url) == 0) && git_path_isdir(url)) + return &git_transport_local; + + /* It could be a SSH remote path. Check to see if there's a : */ + if (strrchr(url, ':')) + return &git_transport_dummy; /* SSH is an unsupported transport mechanism in this version of libgit2 */ + + return NULL; +} + +/************** + * Public API * + **************/ + +int git_transport_dummy(git_transport **transport) +{ + GIT_UNUSED(transport); + giterr_set(GITERR_NET, "This transport isn't implemented. Sorry"); + return -1; +} + +int git_transport_new(git_transport **out, const char *url) +{ + git_transport_cb fn; + git_transport *transport; + int error; + + fn = transport_find_fn(url); + + if (fn == NULL) { + giterr_set(GITERR_NET, "Unsupported URL protocol"); + return -1; + } + + error = fn(&transport); + if (error < 0) + return error; + + transport->url = git__strdup(url); + GITERR_CHECK_ALLOC(transport->url); + + *out = transport; + + return 0; +} + +/* from remote.h */ +int git_remote_valid_url(const char *url) +{ + return transport_find_fn(url) != NULL; +} + +int git_remote_supported_url(const char* url) +{ + git_transport_cb transport_fn = transport_find_fn(url); + + return ((transport_fn != NULL) && (transport_fn != &git_transport_dummy)); +} diff --git a/src/transport.h b/src/transport.h new file mode 100644 index 000000000..68b92f7a6 --- /dev/null +++ b/src/transport.h @@ -0,0 +1,130 @@ +/* + * 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_transport_h__ +#define INCLUDE_transport_h__ + +#include "git2/net.h" +#include "git2/indexer.h" +#include "vector.h" +#include "posix.h" +#include "common.h" +#ifdef GIT_SSL +# include <openssl/ssl.h> +# include <openssl/err.h> +#endif + + +#define GIT_CAP_OFS_DELTA "ofs-delta" + +typedef struct git_transport_caps { + int common:1, + ofs_delta:1; +} git_transport_caps; + +#ifdef GIT_SSL +typedef struct gitno_ssl { + SSL_CTX *ctx; + SSL *ssl; +} gitno_ssl; +#endif + + +/* + * A day in the life of a network operation + * ======================================== + * + * The library gets told to ls-remote/push/fetch on/to/from some + * remote. We look at the URL of the remote and fill the function + * table with whatever is appropriate (the remote may be git over git, + * ssh or http(s). It may even be an hg or svn repository, the library + * at this level doesn't care, it just calls the helpers. + * + * The first call is to ->connect() which connects to the remote, + * making use of the direction if necessary. This function must also + * store the remote heads and any other information it needs. + * + * The next useful step is to call ->ls() to get the list of + * references available to the remote. These references may have been + * collected on connect, or we may build them now. For ls-remote, + * nothing else is needed other than closing the connection. + * Otherwise, the higher leves decide which objects we want to + * have. ->send_have() is used to tell the other end what we have. If + * we do need to download a pack, ->download_pack() is called. + * + * When we're done, we call ->close() to close the + * connection. ->free() takes care of freeing all the resources. + */ + +struct git_transport { + /** + * Where the repo lives + */ + char *url; + /** + * Whether we want to push or fetch + */ + int direction : 1, /* 0 fetch, 1 push */ + connected : 1, + check_cert: 1, + encrypt : 1; +#ifdef GIT_SSL + struct gitno_ssl ssl; +#endif + GIT_SOCKET socket; + /** + * Connect and store the remote heads + */ + int (*connect)(struct git_transport *transport, int dir); + /** + * Give a list of references, useful for ls-remote + */ + int (*ls)(struct git_transport *transport, git_headlist_cb list_cb, void *opaque); + /** + * Push the changes over + */ + int (*push)(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); + /** + * Download the packfile + */ + int (*download_pack)(struct git_transport *transport, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats); + /** + * Fetch the changes + */ + int (*fetch)(struct git_transport *transport); + /** + * Close the connection + */ + int (*close)(struct git_transport *transport); + /** + * Free the associated resources + */ + void (*free)(struct git_transport *transport); +}; + + +int git_transport_new(struct git_transport **transport, const char *url); +int git_transport_local(struct git_transport **transport); +int git_transport_git(struct git_transport **transport); +int git_transport_http(struct git_transport **transport); +int git_transport_https(struct git_transport **transport); +int git_transport_dummy(struct git_transport **transport); + +/** + Returns true if the passed URL is valid (a URL with a Git supported scheme, + or pointing to an existing path) +*/ +int git_transport_valid_url(const char *url); + +typedef struct git_transport git_transport; +typedef int (*git_transport_cb)(git_transport **transport); + +#endif diff --git a/src/transports/git.c b/src/transports/git.c new file mode 100644 index 000000000..45f571f20 --- /dev/null +++ b/src/transports/git.c @@ -0,0 +1,477 @@ +/* + * 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 "git2/net.h" +#include "git2/common.h" +#include "git2/types.h" +#include "git2/errors.h" +#include "git2/net.h" +#include "git2/revwalk.h" + +#include "vector.h" +#include "transport.h" +#include "pkt.h" +#include "common.h" +#include "netops.h" +#include "filebuf.h" +#include "repository.h" +#include "fetch.h" +#include "protocol.h" + +typedef struct { + git_transport parent; + git_protocol proto; + git_vector refs; + git_remote_head **heads; + git_transport_caps caps; + char buff[1024]; + gitno_buffer buf; +#ifdef GIT_WIN32 + WSADATA wsd; +#endif +} transport_git; + +/* + * Create a git procol request. + * + * For example: 0035git-upload-pack /libgit2/libgit2\0host=github.com\0 + */ +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="; + size_t len; + + delim = strchr(url, '/'); + if (delim == NULL) { + giterr_set(GITERR_NET, "Malformed URL"); + return -1; + } + + repo = delim; + + delim = strchr(url, ':'); + if (delim == NULL) + delim = strchr(url, '/'); + + if (cmd == NULL) + cmd = default_command; + + 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", + (unsigned int)(len & 0x0FFFF), cmd, repo, 0, host); + git_buf_put(request, url, delim - url); + git_buf_putc(request, '\0'); + + if (git_buf_oom(request)) + return -1; + + return 0; +} + +static int send_request(git_transport *t, const char *cmd, const char *url) +{ + int error; + git_buf request = GIT_BUF_INIT; + + error = gen_proto(&request, cmd, url); + if (error < 0) + goto cleanup; + + error = gitno_send(t, request.ptr, request.size, 0); + +cleanup: + git_buf_free(&request); + return error; +} + +/* + * Parse the URL and connect to a server, storing the socket in + * out. For convenience this also takes care of asking for the remote + * refs + */ +static int do_connect(transport_git *t, const char *url) +{ + char *host, *port; + const char prefix[] = "git://"; + + if (!git__prefixcmp(url, prefix)) + url += strlen(prefix); + + if (gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT) < 0) + return -1; + + if (gitno_connect((git_transport *)t, host, port) < 0) + goto on_error; + + if (send_request((git_transport *)t, NULL, url) < 0) + goto on_error; + + git__free(host); + git__free(port); + + return 0; + +on_error: + git__free(host); + git__free(port); + gitno_close(t->parent.socket); + return -1; +} + +/* + * Read from the socket and store the references in the vector + */ +static int store_refs(transport_git *t) +{ + gitno_buffer *buf = &t->buf; + int ret = 0; + + while (1) { + 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_EBUFS) { + gitno_consume_n(buf, buf->len); + continue; + } + + if (ret < 0) + return ret; + + gitno_consume_n(buf, buf->offset); + + if (t->proto.flush) { /* No more refs */ + t->proto.flush = 0; + return 0; + } + } +} + +static int detect_caps(transport_git *t) +{ + git_vector *refs = &t->refs; + git_pkt_ref *pkt; + git_transport_caps *caps = &t->caps; + const char *ptr; + + pkt = git_vector_get(refs, 0); + /* No refs or capabilites, odd but not a problem */ + if (pkt == NULL || pkt->capabilities == NULL) + return 0; + + ptr = pkt->capabilities; + while (ptr != NULL && *ptr != '\0') { + if (*ptr == ' ') + ptr++; + + if(!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) { + caps->common = caps->ofs_delta = 1; + ptr += strlen(GIT_CAP_OFS_DELTA); + continue; + } + + /* We don't know this capability, so skip it */ + ptr = strchr(ptr, ' '); + } + + return 0; +} + +/* + * Since this is a network connection, we need to parse and store the + * pkt-lines at this stage and keep them there. + */ +static int git_connect(git_transport *transport, int direction) +{ + transport_git *t = (transport_git *) transport; + + if (direction == GIT_DIR_PUSH) { + giterr_set(GITERR_NET, "Pushing over git:// is not supported"); + return -1; + } + + t->parent.direction = direction; + if (git_vector_init(&t->refs, 16, NULL) < 0) + return -1; + + /* Connect and ask for the refs */ + if (do_connect(t, transport->url) < 0) + goto cleanup; + + gitno_buffer_setup(transport, &t->buf, t->buff, sizeof(t->buff)); + + t->parent.connected = 1; + if (store_refs(t) < 0) + goto cleanup; + + if (detect_caps(t) < 0) + goto cleanup; + + return 0; +cleanup: + git_vector_free(&t->refs); + return -1; +} + +static int git_ls(git_transport *transport, git_headlist_cb list_cb, void *opaque) +{ + transport_git *t = (transport_git *) transport; + git_vector *refs = &t->refs; + unsigned int i; + git_pkt *p = NULL; + + git_vector_foreach(refs, i, p) { + git_pkt_ref *pkt = NULL; + + if (p->type != GIT_PKT_REF) + continue; + + pkt = (git_pkt_ref *)p; + + if (list_cb(&pkt->head, opaque) < 0) { + giterr_set(GITERR_NET, "User callback returned error"); + return -1; + } + } + + 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_EBUFS) + 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_oid oid; + int error; + unsigned int i; + git_buf data = GIT_BUF_INIT; + gitno_buffer *buf = &t->buf; + + if (git_pkt_buffer_wants(wants, &t->caps, &data) < 0) + return -1; + + if (git_fetch_setup_walk(&walk, repo) < 0) + goto on_error; + + if (gitno_send(transport, 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)) == 0) { + git_pkt_buffer_have(&oid, &data); + i++; + if (i % 20 == 0) { + int pkt_type; + + git_pkt_buffer_flush(&data); + if (git_buf_oom(&data)) + goto on_error; + + if (gitno_send(transport, data.ptr, data.size, 0) < 0) + goto on_error; + + pkt_type = recv_pkt(buf); + + 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; + } + + } + } + if (error < 0 && error != GIT_REVWALKOVER) + goto on_error; + + /* 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(transport, data.ptr, data.size, 0) < 0) + goto on_error; + + 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(git_transport *transport, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats) +{ + transport_git *t = (transport_git *) transport; + int error = 0, read_bytes; + gitno_buffer *buf = &t->buf; + git_pkt *pkt; + const char *line_end, *ptr; + + /* + * For now, we ignore everything and wait for the pack + */ + do { + ptr = buf->data; + /* Whilst we're searching for the pack */ + while (1) { + if (buf->offset == 0) { + break; + } + + error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset); + if (error == GIT_EBUFS) + break; + + if (error < 0) + return error; + + if (pkt->type == GIT_PKT_PACK) { + git__free(pkt); + return git_fetch__download_pack(buf->data, buf->offset, transport, repo, bytes, stats); + } + + /* For now we don't care about anything */ + git__free(pkt); + gitno_consume(buf, line_end); + } + + read_bytes = gitno_recv(buf); + } while (read_bytes); + + return read_bytes; +} + +static int git_close(git_transport *t) +{ + git_buf buf = GIT_BUF_INIT; + + if (git_pkt_buffer_flush(&buf) < 0) + return -1; + /* Can't do anything if there's an error, so don't bother checking */ + gitno_send(t, buf.ptr, buf.size, 0); + + if (gitno_close(t->socket) < 0) { + giterr_set(GITERR_NET, "Failed to close socket"); + return -1; + } + + t->connected = 0; + +#ifdef GIT_WIN32 + WSACleanup(); +#endif + + return 0; +} + +static void git_free(git_transport *transport) +{ + transport_git *t = (transport_git *) transport; + git_vector *refs = &t->refs; + unsigned int i; + + for (i = 0; i < refs->length; ++i) { + git_pkt *p = git_vector_get(refs, i); + git_pkt_free(p); + } + + git_vector_free(refs); + git__free(t->heads); + git_buf_free(&t->proto.buf); + git__free(t->parent.url); + git__free(t); +} + +int git_transport_git(git_transport **out) +{ + transport_git *t; +#ifdef GIT_WIN32 + int ret; +#endif + + t = git__malloc(sizeof(transport_git)); + 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.download_pack = git_download_pack; + t->parent.close = git_close; + t->parent.free = git_free; + t->proto.refs = &t->refs; + t->proto.transport = (git_transport *) t; + + *out = (git_transport *) t; + +#ifdef GIT_WIN32 + ret = WSAStartup(MAKEWORD(2,2), &t->wsd); + if (ret != 0) { + git_free(*out); + giterr_set(GITERR_NET, "Winsock init failed"); + return -1; + } +#endif + + return 0; +} diff --git a/src/transports/http.c b/src/transports/http.c new file mode 100644 index 000000000..4139a2fa6 --- /dev/null +++ b/src/transports/http.c @@ -0,0 +1,707 @@ +/* + * 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 <stdlib.h> +#include "git2.h" +#include "http_parser.h" + +#include "transport.h" +#include "common.h" +#include "netops.h" +#include "buffer.h" +#include "pkt.h" +#include "refs.h" +#include "pack.h" +#include "fetch.h" +#include "filebuf.h" +#include "repository.h" +#include "protocol.h" + +enum last_cb { + NONE, + FIELD, + VALUE +}; + +typedef struct { + git_transport parent; + git_protocol proto; + git_vector refs; + git_vector common; + git_buf buf; + git_remote_head **heads; + int error; + int transfer_finished :1, + ct_found :1, + ct_finished :1, + pack_ready :1; + enum last_cb last_cb; + http_parser parser; + char *content_type; + char *path; + char *host; + char *port; + char *service; + git_transport_caps caps; +#ifdef GIT_WIN32 + WSADATA wsd; +#endif +} transport_http; + +static int gen_request(git_buf *buf, const char *path, const char *host, const char *op, + const char *service, ssize_t content_length, int ls) +{ + if (path == NULL) /* Is 'git fetch http://host.com/' valid? */ + path = "/"; + + if (ls) { + git_buf_printf(buf, "%s %s/info/refs?service=git-%s HTTP/1.1\r\n", op, path, service); + } else { + git_buf_printf(buf, "%s %s/git-%s HTTP/1.1\r\n", op, path, service); + } + git_buf_puts(buf, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION ")\r\n"); + git_buf_printf(buf, "Host: %s\r\n", host); + if (content_length > 0) { + git_buf_printf(buf, "Accept: application/x-git-%s-result\r\n", service); + git_buf_printf(buf, "Content-Type: application/x-git-%s-request\r\n", service); + git_buf_printf(buf, "Content-Length: %"PRIuZ "\r\n", content_length); + } else { + git_buf_puts(buf, "Accept: */*\r\n"); + } + git_buf_puts(buf, "\r\n"); + + if (git_buf_oom(buf)) + return -1; + + return 0; +} + +static int do_connect(transport_http *t, const char *host, const char *port) +{ + if (t->parent.connected && http_should_keep_alive(&t->parser)) + return 0; + + if (gitno_connect((git_transport *) t, host, port) < 0) + return -1; + + t->parent.connected = 1; + + return 0; +} + +/* + * The HTTP parser is streaming, so we need to wait until we're in the + * field handler before we can be sure that we can store the previous + * value. Right now, we only care about the + * Content-Type. on_header_{field,value} should be kept generic enough + * to work for any request. + */ + +static const char *typestr = "Content-Type"; + +static int on_header_field(http_parser *parser, const char *str, size_t len) +{ + transport_http *t = (transport_http *) parser->data; + git_buf *buf = &t->buf; + + if (t->last_cb == VALUE && t->ct_found) { + t->ct_finished = 1; + t->ct_found = 0; + t->content_type = git__strdup(git_buf_cstr(buf)); + GITERR_CHECK_ALLOC(t->content_type); + git_buf_clear(buf); + } + + if (t->ct_found) { + t->last_cb = FIELD; + return 0; + } + + if (t->last_cb != FIELD) + git_buf_clear(buf); + + git_buf_put(buf, str, len); + t->last_cb = FIELD; + + return git_buf_oom(buf); +} + +static int on_header_value(http_parser *parser, const char *str, size_t len) +{ + transport_http *t = (transport_http *) parser->data; + git_buf *buf = &t->buf; + + if (t->ct_finished) { + t->last_cb = VALUE; + return 0; + } + + if (t->last_cb == VALUE) + git_buf_put(buf, str, len); + + if (t->last_cb == FIELD && !strcmp(git_buf_cstr(buf), typestr)) { + t->ct_found = 1; + git_buf_clear(buf); + git_buf_put(buf, str, len); + } + + t->last_cb = VALUE; + + return git_buf_oom(buf); +} + +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) + return t->error = -1; + } + + git_buf_clear(buf); + git_buf_printf(buf, "application/x-git-%s-advertisement", t->service); + if (git_buf_oom(buf)) + return t->error = -1; + + if (strcmp(t->content_type, git_buf_cstr(buf))) + return t->error = -1; + + git_buf_clear(buf); + return 0; +} + +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); +} + +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) +{ + 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; + memset(&settings, 0x0, sizeof(http_parser_settings)); + settings.on_header_field = on_header_field; + settings.on_header_value = on_header_value; + settings.on_headers_complete = on_headers_complete; + settings.on_body = on_body_store_refs; + settings.on_message_complete = on_message_complete; + + gitno_buffer_setup((git_transport *)t, &buf, buffer, sizeof(buffer)); + + while(1) { + size_t parsed; + + 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 < 0) + return t->error; + + gitno_consume_n(&buf, parsed); + + if (ret == 0 || t->transfer_finished) + return 0; + } + + pkt = git_vector_get(&t->refs, 0); + 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 0; +} + +static int http_connect(git_transport *transport, int direction) +{ + transport_http *t = (transport_http *) transport; + int ret; + git_buf request = GIT_BUF_INIT; + const char *service = "upload-pack"; + const char *url = t->parent.url, *prefix_http = "http://", *prefix_https = "https://"; + const char *default_port; + + if (direction == GIT_DIR_PUSH) { + giterr_set(GITERR_NET, "Pushing over HTTP is not implemented"); + return -1; + } + + t->parent.direction = direction; + if (git_vector_init(&t->refs, 16, NULL) < 0) + return -1; + + if (!git__prefixcmp(url, prefix_http)) { + url = t->parent.url + strlen(prefix_http); + default_port = "80"; + } + + if (!git__prefixcmp(url, prefix_https)) { + url += strlen(prefix_https); + default_port = "443"; + } + + t->path = strchr(url, '/'); + + if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, default_port)) < 0) + goto cleanup; + + t->service = git__strdup(service); + GITERR_CHECK_ALLOC(t->service); + + if ((ret = do_connect(t, t->host, t->port)) < 0) + goto cleanup; + + /* Generate and send the HTTP request */ + if ((ret = gen_request(&request, t->path, t->host, "GET", service, 0, 1)) < 0) { + giterr_set(GITERR_NET, "Failed to generate request"); + goto cleanup; + } + + + if (gitno_send(transport, request.ptr, request.size, 0) < 0) + goto cleanup; + + ret = store_refs(t); + +cleanup: + git_buf_free(&request); + git_buf_clear(&t->buf); + + return ret; +} + +static int http_ls(git_transport *transport, git_headlist_cb list_cb, void *opaque) +{ + transport_http *t = (transport_http *) transport; + git_vector *refs = &t->refs; + unsigned int i; + git_pkt_ref *p; + + git_vector_foreach(refs, i, p) { + if (p->type != GIT_PKT_REF) + continue; + + if (list_cb(&p->head, opaque) < 0) { + giterr_set(GITERR_NET, "The user callback returned error"); + return -1; + } + } + + return 0; +} + +static int on_body_parse_response(http_parser *parser, const char *str, size_t len) +{ + transport_http *t = (transport_http *) parser->data; + git_buf *buf = &t->buf; + git_vector *common = &t->common; + int error; + const char *line_end, *ptr; + + if (len == 0) { /* EOF */ + 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); + ptr = buf->ptr; + while (1) { + git_pkt *pkt; + + if (git_buf_len(buf) == 0) + return 0; + + error = git_pkt_parse_line(&pkt, ptr, &line_end, git_buf_len(buf)); + if (error == GIT_EBUFS) { + return 0; /* Ask for more */ + } + if (error < 0) + return t->error = -1; + + git_buf_consume(buf, line_end); + + if (pkt->type == GIT_PKT_PACK) { + git__free(pkt); + t->pack_ready = 1; + return 0; + } + + if (pkt->type == GIT_PKT_NAK) { + git__free(pkt); + return 0; + } + + if (pkt->type != GIT_PKT_ACK) { + git__free(pkt); + continue; + } + + if (git_vector_insert(common, pkt) < 0) + return -1; + } + + return error; + +} + +static int parse_response(transport_http *t) +{ + int ret = 0; + http_parser_settings settings; + char buffer[1024]; + gitno_buffer buf; + + http_parser_init(&t->parser, HTTP_RESPONSE); + t->parser.data = t; + t->transfer_finished = 0; + memset(&settings, 0x0, sizeof(http_parser_settings)); + settings.on_header_field = on_header_field; + settings.on_header_value = on_header_value; + settings.on_headers_complete = on_headers_complete; + settings.on_body = on_body_parse_response; + settings.on_message_complete = on_message_complete; + + gitno_buffer_setup((git_transport *)t, &buf, buffer, sizeof(buffer)); + + while(1) { + size_t parsed; + + 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 < 0) + return t->error; + + gitno_consume_n(&buf, parsed); + + if (ret == 0 || t->transfer_finished || t->pack_ready) { + return 0; + } + } + + return ret; +} + +static int http_negotiate_fetch(git_transport *transport, git_repository *repo, const git_vector *wants) +{ + transport_http *t = (transport_http *) transport; + int ret; + unsigned int i; + char buff[128]; + gitno_buffer buf; + git_revwalk *walk = NULL; + git_oid oid; + git_pkt_ack *pkt; + git_vector *common = &t->common; + git_buf request = GIT_BUF_INIT, data = GIT_BUF_INIT; + + gitno_buffer_setup(transport, &buf, buff, sizeof(buff)); + + if (git_vector_init(common, 16, NULL) < 0) + return -1; + + if (git_fetch_setup_walk(&walk, repo) < 0) + return -1; + + do { + if ((ret = do_connect(t, t->host, t->port)) < 0) + goto cleanup; + + 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) { + if ((ret = git_pkt_buffer_have(&pkt->oid, &data)) < 0) + goto cleanup; + } + + i = 0; + 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); + + if ((ret = gen_request(&request, t->path, t->host, "POST", "upload-pack", data.size, 0)) < 0) + goto cleanup; + + if ((ret = gitno_send(transport, request.ptr, request.size, 0)) < 0) + goto cleanup; + + if ((ret = gitno_send(transport, data.ptr, data.size, 0)) < 0) + goto cleanup; + + git_buf_clear(&request); + git_buf_clear(&data); + + if (ret < 0 || i >= 256) + break; + + if ((ret = parse_response(t)) < 0) + goto cleanup; + + if (t->pack_ready) { + ret = 0; + goto cleanup; + } + + } while(1); + +cleanup: + git_buf_free(&request); + git_buf_free(&data); + git_revwalk_free(walk); + return ret; +} + +typedef struct { + git_indexer_stream *idx; + git_indexer_stats *stats; + transport_http *transport; +} download_pack_cbdata; + +static int on_message_complete_download_pack(http_parser *parser) +{ + download_pack_cbdata *data = (download_pack_cbdata *) parser->data; + + data->transport->transfer_finished = 1; + + return 0; +} +static int on_body_download_pack(http_parser *parser, const char *str, size_t len) +{ + download_pack_cbdata *data = (download_pack_cbdata *) parser->data; + transport_http *t = data->transport; + git_indexer_stream *idx = data->idx; + git_indexer_stats *stats = data->stats; + + return t->error = git_indexer_stream_add(idx, str, len, stats); +} + +/* + * As the server is probably using Transfer-Encoding: chunked, we have + * to use the HTTP parser to download the pack instead of giving it to + * the simple downloader. Furthermore, we're using keep-alive + * connections, so the simple downloader would just hang. + */ +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 recvd; + http_parser_settings settings; + char buffer[1024]; + gitno_buffer buf; + git_indexer_stream *idx = NULL; + download_pack_cbdata data; + + gitno_buffer_setup(transport, &buf, buffer, sizeof(buffer)); + + 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. + */ + 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); + + if (git_indexer_stream_add(idx, git_buf_cstr(oldbuf), git_buf_len(oldbuf), stats) < 0) + goto on_error; + + gitno_buffer_setup(transport, &buf, buffer, sizeof(buffer)); + + do { + size_t parsed; + + if ((recvd = gitno_recv(&buf)) < 0) + goto on_error; + + parsed = http_parser_execute(&t->parser, &settings, buf.data, buf.offset); + if (parsed != buf.offset || t->error < 0) + goto on_error; + + *bytes += recvd; + gitno_consume_n(&buf, parsed); + } while (recvd > 0 && !t->transfer_finished); + + if (git_indexer_stream_finalize(idx, stats) < 0) + goto on_error; + + git_indexer_stream_free(idx); + return 0; + +on_error: + git_indexer_stream_free(idx); + return -1; +} + +static int http_close(git_transport *transport) +{ + if (gitno_ssl_teardown(transport) < 0) + return -1; + + if (gitno_close(transport->socket) < 0) { + giterr_set(GITERR_OS, "Failed to close the socket: %s", strerror(errno)); + return -1; + } + + transport->connected = 0; + + return 0; +} + + +static void http_free(git_transport *transport) +{ + transport_http *t = (transport_http *) transport; + git_vector *refs = &t->refs; + git_vector *common = &t->common; + unsigned int i; + git_pkt *p; + +#ifdef GIT_WIN32 + /* cleanup the WSA context. note that this context + * can be initialized more than once with WSAStartup(), + * and needs to be cleaned one time for each init call + */ + WSACleanup(); +#endif + + git_vector_foreach(refs, i, p) { + git_pkt_free(p); + } + git_vector_free(refs); + git_vector_foreach(common, i, p) { + git_pkt_free(p); + } + git_vector_free(common); + git_buf_free(&t->buf); + git_buf_free(&t->proto.buf); + git__free(t->heads); + git__free(t->content_type); + git__free(t->host); + git__free(t->port); + git__free(t->service); + git__free(t->parent.url); + git__free(t); +} + +int git_transport_http(git_transport **out) +{ + transport_http *t; + + t = git__malloc(sizeof(transport_http)); + GITERR_CHECK_ALLOC(t); + + memset(t, 0x0, sizeof(transport_http)); + + t->parent.connect = http_connect; + t->parent.ls = http_ls; + t->parent.negotiate_fetch = http_negotiate_fetch; + t->parent.download_pack = http_download_pack; + t->parent.close = http_close; + t->parent.free = http_free; + t->proto.refs = &t->refs; + t->proto.transport = (git_transport *) t; + +#ifdef GIT_WIN32 + /* on win32, the WSA context needs to be initialized + * before any socket calls can be performed */ + if (WSAStartup(MAKEWORD(2,2), &t->wsd) != 0) { + http_free((git_transport *) t); + giterr_set(GITERR_OS, "Winsock init failed"); + return -1; + } +#endif + + *out = (git_transport *) t; + return 0; +} + +int git_transport_https(git_transport **out) +{ +#ifdef GIT_SSL + transport_http *t; + if (git_transport_http((git_transport **)&t) < 0) + return -1; + + t->parent.encrypt = 1; + t->parent.check_cert = 1; + *out = (git_transport *) t; + + return 0; +#else + GIT_UNUSED(out); + + giterr_set(GITERR_NET, "HTTPS support not available"); + return -1; +#endif +} diff --git a/src/transports/local.c b/src/transports/local.c new file mode 100644 index 000000000..0e1ae3752 --- /dev/null +++ b/src/transports/local.c @@ -0,0 +1,241 @@ +/* + * 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 "git2/types.h" +#include "git2/net.h" +#include "git2/repository.h" +#include "git2/object.h" +#include "git2/tag.h" +#include "refs.h" +#include "transport.h" +#include "posix.h" +#include "path.h" +#include "buffer.h" + +typedef struct { + git_transport parent; + git_repository *repo; + git_vector refs; +} transport_local; + +static int add_ref(transport_local *t, const char *name) +{ + const char peeled[] = "^{}"; + git_remote_head *head; + git_object *obj = NULL, *target = NULL; + git_buf buf = GIT_BUF_INIT; + + head = git__malloc(sizeof(git_remote_head)); + GITERR_CHECK_ALLOC(head); + + head->name = git__strdup(name); + GITERR_CHECK_ALLOC(head->name); + + 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)) + return 0; + + 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) { + 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)); + GITERR_CHECK_ALLOC(head); + if (git_buf_join(&buf, 0, name, peeled) < 0) + return -1; + + head->name = git_buf_detach(&buf); + + if (git_tag_peel(&target, (git_tag *) obj) < 0) + goto on_error; + + git_oid_cpy(&head->oid, git_object_id(target)); + git_object_free(obj); + git_object_free(target); + + if (git_vector_insert(&t->refs, head) < 0) + return -1; + + return 0; + +on_error: + git_object_free(obj); + git_object_free(target); + return -1; +} + +static int store_refs(transport_local *t) +{ + unsigned int i; + git_strarray ref_names = {0}; + + assert(t); + + if (git_reference_list(&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 */ + if (add_ref(t, GIT_HEAD_FILE) < 0) + goto on_error; + + for (i = 0; i < ref_names.count; ++i) { + if (add_ref(t, ref_names.strings[i]) < 0) + goto on_error; + } + + git_strarray_free(&ref_names); + 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) +{ + transport_local *t = (transport_local *) transport; + git_vector *refs = &t->refs; + unsigned int i; + git_remote_head *h; + + assert(transport && transport->connected); + + git_vector_foreach(refs, i, h) { + if (list_cb(h, payload) < 0) + return -1; + } + + return 0; +} + +/* + * Try to open the url as a git directory. The direction doesn't + * matter in this case because we're calulating the heads ourselves. + */ +static int local_connect(git_transport *transport, int direction) +{ + git_repository *repo; + int error; + transport_local *t = (transport_local *) transport; + const char *path; + git_buf buf = GIT_BUF_INIT; + + GIT_UNUSED(direction); + + /* The repo layer doesn't want the prefix */ + if (!git__prefixcmp(transport->url, "file://")) { + if (git_path_fromurl(&buf, transport->url) < 0) { + git_buf_free(&buf); + return -1; + } + path = git_buf_cstr(&buf); + + } else { /* We assume transport->url is already a path */ + path = transport->url; + } + + error = git_repository_open(&repo, path); + + git_buf_free(&buf); + + if (error < 0) + return -1; + + t->repo = repo; + + if (store_refs(t) < 0) + return -1; + + t->parent.connected = 1; + + return 0; +} + +static int local_negotiate_fetch(git_transport *transport, git_repository *repo, const git_vector *wants) +{ + GIT_UNUSED(transport); + GIT_UNUSED(repo); + GIT_UNUSED(wants); + + giterr_set(GITERR_NET, "Fetch via local transport isn't implemented. Sorry"); + return -1; +} + +static int local_close(git_transport *transport) +{ + transport_local *t = (transport_local *)transport; + + t->parent.connected = 0; + git_repository_free(t->repo); + t->repo = NULL; + + return 0; +} + +static void local_free(git_transport *transport) +{ + unsigned int i; + transport_local *t = (transport_local *) transport; + git_vector *vec = &t->refs; + git_remote_head *h; + + assert(transport); + + git_vector_foreach (vec, i, h) { + git__free(h->name); + git__free(h); + } + git_vector_free(vec); + + git__free(t->parent.url); + git__free(t); +} + +/************** + * Public API * + **************/ + +int git_transport_local(git_transport **out) +{ + transport_local *t; + + t = git__malloc(sizeof(transport_local)); + GITERR_CHECK_ALLOC(t); + + memset(t, 0x0, sizeof(transport_local)); + + t->parent.connect = local_connect; + t->parent.ls = local_ls; + t->parent.negotiate_fetch = local_negotiate_fetch; + t->parent.close = local_close; + t->parent.free = local_free; + + *out = (git_transport *) t; + + return 0; +} diff --git a/src/tree-cache.c b/src/tree-cache.c new file mode 100644 index 000000000..ebc2c6807 --- /dev/null +++ b/src/tree-cache.c @@ -0,0 +1,184 @@ +/* + * 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 "tree-cache.h" + +static git_tree_cache *find_child(const git_tree_cache *tree, const char *path) +{ + size_t i, dirlen; + const char *end; + + end = strchr(path, '/'); + if (end == NULL) { + end = strrchr(path, '\0'); + } + + dirlen = end - path; + + for (i = 0; i < tree->children_count; ++i) { + const char *childname = tree->children[i]->name; + + if (strlen(childname) == dirlen && !memcmp(path, childname, dirlen)) + return tree->children[i]; + } + + return NULL; +} + +void git_tree_cache_invalidate_path(git_tree_cache *tree, const char *path) +{ + const char *ptr = path, *end; + + if (tree == NULL) + return; + + tree->entries = -1; + + while (ptr != NULL) { + end = strchr(ptr, '/'); + + if (end == NULL) /* End of path */ + break; + + tree = find_child(tree, ptr); + if (tree == NULL) /* We don't have that tree */ + return; + + tree->entries = -1; + ptr = end + 1; + } +} + +const git_tree_cache *git_tree_cache_get(const git_tree_cache *tree, const char *path) +{ + const char *ptr = path, *end; + + if (tree == NULL) { + return NULL; + } + + while (1) { + end = strchr(ptr, '/'); + + tree = find_child(tree, ptr); + if (tree == NULL) { /* Can't find it */ + return NULL; + } + + if (end == NULL || end + 1 == '\0') + return tree; + + ptr = end + 1; + } +} + +static int read_tree_internal(git_tree_cache **out, + const char **buffer_in, const char *buffer_end, git_tree_cache *parent) +{ + git_tree_cache *tree = NULL; + const char *name_start, *buffer; + int count; + size_t name_len; + + buffer = name_start = *buffer_in; + + if ((buffer = memchr(buffer, '\0', buffer_end - buffer)) == NULL) + goto corrupted; + + if (++buffer >= buffer_end) + goto corrupted; + + name_len = strlen(name_start); + tree = git__malloc(sizeof(git_tree_cache) + name_len + 1); + GITERR_CHECK_ALLOC(tree); + + memset(tree, 0x0, sizeof(git_tree_cache)); + tree->parent = parent; + + /* NUL-terminated tree name */ + memcpy(tree->name, name_start, name_len); + tree->name[name_len] = '\0'; + + /* Blank-terminated ASCII decimal number of entries in this tree */ + if (git__strtol32(&count, buffer, &buffer, 10) < 0 || count < -1) + goto corrupted; + + tree->entries = count; + + if (*buffer != ' ' || ++buffer >= buffer_end) + goto corrupted; + + /* Number of children of the tree, newline-terminated */ + if (git__strtol32(&count, buffer, &buffer, 10) < 0 || count < 0) + goto corrupted; + + tree->children_count = count; + + if (*buffer != '\n' || ++buffer > buffer_end) + goto corrupted; + + /* The SHA1 is only there if it's not invalidated */ + if (tree->entries >= 0) { + /* 160-bit SHA-1 for this tree and it's children */ + if (buffer + GIT_OID_RAWSZ > buffer_end) + goto corrupted; + + git_oid_fromraw(&tree->oid, (const unsigned char *)buffer); + buffer += GIT_OID_RAWSZ; + } + + /* Parse children: */ + if (tree->children_count > 0) { + unsigned int i; + + tree->children = git__malloc(tree->children_count * sizeof(git_tree_cache *)); + GITERR_CHECK_ALLOC(tree->children); + + for (i = 0; i < tree->children_count; ++i) { + if (read_tree_internal(&tree->children[i], &buffer, buffer_end, tree) < 0) + return -1; + } + } + + *buffer_in = buffer; + *out = tree; + return 0; + + corrupted: + git_tree_cache_free(tree); + giterr_set(GITERR_INDEX, "Corruped TREE extension in index"); + return -1; +} + +int git_tree_cache_read(git_tree_cache **tree, const char *buffer, size_t buffer_size) +{ + const char *buffer_end = buffer + buffer_size; + + if (read_tree_internal(tree, &buffer, buffer_end, NULL) < 0) + return -1; + + if (buffer < buffer_end) { + giterr_set(GITERR_INDEX, "Corruped TREE extension in index (unexpected trailing data)"); + return -1; + } + + return 0; +} + +void git_tree_cache_free(git_tree_cache *tree) +{ + unsigned int i; + + if (tree == NULL) + return; + + for (i = 0; i < tree->children_count; ++i) + git_tree_cache_free(tree->children[i]); + + git__free(tree->children); + git__free(tree); +} diff --git a/src/tree-cache.h b/src/tree-cache.h new file mode 100644 index 000000000..41fde997a --- /dev/null +++ b/src/tree-cache.h @@ -0,0 +1,31 @@ +/* + * 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_tree_cache_h__ +#define INCLUDE_tree_cache_h__ + +#include "common.h" +#include "git2/oid.h" + +struct git_tree_cache { + struct git_tree_cache *parent; + struct git_tree_cache **children; + size_t children_count; + + ssize_t entries; + git_oid oid; + char name[GIT_FLEX_ARRAY]; +}; + +typedef struct git_tree_cache git_tree_cache; + +int git_tree_cache_read(git_tree_cache **tree, const char *buffer, size_t buffer_size); +void git_tree_cache_invalidate_path(git_tree_cache *tree, const char *path); +const git_tree_cache *git_tree_cache_get(const git_tree_cache *tree, const char *path); +void git_tree_cache_free(git_tree_cache *tree); + +#endif diff --git a/src/tree.c b/src/tree.c index 60413e276..92b1b1e39 100644 --- a/src/tree.c +++ b/src/tree.c @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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" @@ -33,33 +15,109 @@ #define MAX_FILEMODE 0777777 #define MAX_FILEMODE_BYTES 6 -static int valid_attributes(const int attributes) { - return attributes >= 0 && attributes <= MAX_FILEMODE; +static int valid_attributes(const int attributes) +{ + return attributes >= 0 && attributes <= MAX_FILEMODE; +} + +static int valid_entry_name(const char *filename) +{ + return strlen(filename) > 0 && strchr(filename, '/') == NULL; } -int entry_search_cmp(const void *key, const void *array_member) +static int entry_sort_cmp(const void *a, const void *b) { - const char *filename = (const char *)key; - const git_tree_entry *entry = *(const git_tree_entry **)(array_member); + const git_tree_entry *entry_a = (const git_tree_entry *)(a); + const git_tree_entry *entry_b = (const git_tree_entry *)(b); - return strcmp(filename, entry->filename); + return git_path_cmp( + entry_a->filename, entry_a->filename_len, git_tree_entry__is_tree(entry_a), + entry_b->filename, entry_b->filename_len, git_tree_entry__is_tree(entry_b)); } -#if 0 -static int valid_attributes(const int attributes) { - return attributes >= 0 && attributes <= MAX_FILEMODE; + +struct tree_key_search { + const char *filename; + size_t filename_len; +}; + +static int homing_search_cmp(const void *key, const void *array_member) +{ + const struct tree_key_search *ksearch = key; + const git_tree_entry *entry = array_member; + + const size_t len1 = ksearch->filename_len; + const size_t len2 = entry->filename_len; + + return memcmp( + ksearch->filename, + entry->filename, + len1 < len2 ? len1 : len2 + ); } -#endif -int entry_sort_cmp(const void *a, const void *b) +/* + * Search for an entry in a given tree. + * + * Note that this search is performed in two steps because + * of the way tree entries are sorted internally in git: + * + * Entries in a tree are not sorted alphabetically; two entries + * with the same root prefix will have different positions + * depending on whether they are folders (subtrees) or normal files. + * + * Consequently, it is not possible to find an entry on the tree + * with a binary search if you don't know whether the filename + * you're looking for is a folder or a normal file. + * + * To work around this, we first perform a homing binary search + * on the tree, using the minimal length root prefix of our filename. + * Once the comparisons for this homing search start becoming + * ambiguous because of folder vs file sorting, we look linearly + * around the area for our target file. + */ +static int tree_key_search(git_vector *entries, const char *filename) { - const git_tree_entry *entry_a = *(const git_tree_entry **)(a); - const git_tree_entry *entry_b = *(const git_tree_entry **)(b); + struct tree_key_search ksearch; + const git_tree_entry *entry; - return gitfo_cmp_path(entry_a->filename, strlen(entry_a->filename), - entry_a->attr & 040000, - entry_b->filename, strlen(entry_b->filename), - entry_b->attr & 040000); + int homing, i; + + ksearch.filename = filename; + ksearch.filename_len = strlen(filename); + + /* Initial homing search; find an entry on the tree with + * the same prefix as the filename we're looking for */ + homing = git_vector_bsearch2(entries, &homing_search_cmp, &ksearch); + if (homing < 0) + return homing; + + /* We found a common prefix. Look forward as long as + * there are entries that share the common prefix */ + for (i = homing; i < (int)entries->length; ++i) { + entry = entries->contents[i]; + + if (homing_search_cmp(&ksearch, entry) < 0) + break; + + if (strcmp(filename, entry->filename) == 0) + return i; + } + + /* If we haven't found our filename yet, look backwards + * too as long as we have entries with the same prefix */ + for (i = homing - 1; i >= 0; --i) { + entry = entries->contents[i]; + + if (homing_search_cmp(&ksearch, entry) > 0) + break; + + if (strcmp(filename, entry->filename) == 0) + return i; + } + + /* The filename doesn't exist at all */ + return GIT_ENOTFOUND; } void git_tree__free(git_tree *tree) @@ -70,12 +128,12 @@ void git_tree__free(git_tree *tree) git_tree_entry *e; e = git_vector_get(&tree->entries, i); - free(e->filename); - free(e); + git__free(e->filename); + git__free(e); } git_vector_free(&tree->entries); - free(tree); + git__free(tree); } const git_oid *git_tree_id(git_tree *c) @@ -100,7 +158,22 @@ const git_oid *git_tree_entry_id(const git_tree_entry *entry) return &entry->oid; } -int git_tree_entry_2object(git_object **object_out, git_repository *repo, const git_tree_entry *entry) +git_otype git_tree_entry_type(const git_tree_entry *entry) +{ + assert(entry); + + if (S_ISGITLINK(entry->attr)) + return GIT_OBJ_COMMIT; + else if (S_ISDIR(entry->attr)) + return GIT_OBJ_TREE; + else + return GIT_OBJ_BLOB; +} + +int git_tree_entry_to_object( + git_object **object_out, + git_repository *repo, + const git_tree_entry *entry) { assert(entry && object_out); return git_object_lookup(object_out, repo, &entry->oid, GIT_OBJ_ANY); @@ -112,56 +185,84 @@ const git_tree_entry *git_tree_entry_byname(git_tree *tree, const char *filename assert(tree && filename); - idx = git_vector_bsearch2(&tree->entries, entry_search_cmp, filename); + idx = tree_key_search(&tree->entries, filename); if (idx == GIT_ENOTFOUND) return NULL; return git_vector_get(&tree->entries, idx); } -const git_tree_entry *git_tree_entry_byindex(git_tree *tree, int idx) +const git_tree_entry *git_tree_entry_byindex(git_tree *tree, unsigned int idx) { assert(tree); - return git_vector_get(&tree->entries, (unsigned int)idx); + return git_vector_get(&tree->entries, idx); } -size_t git_tree_entrycount(git_tree *tree) +int git_tree__prefix_position(git_tree *tree, const char *path) +{ + git_vector *entries = &tree->entries; + struct tree_key_search ksearch; + unsigned int at_pos; + + ksearch.filename = path; + ksearch.filename_len = strlen(path); + + /* Find tree entry with appropriate prefix */ + git_vector_bsearch3(&at_pos, entries, &homing_search_cmp, &ksearch); + + for (; at_pos < entries->length; ++at_pos) { + const git_tree_entry *entry = entries->contents[at_pos]; + if (homing_search_cmp(&ksearch, entry) < 0) + break; + } + + for (; at_pos > 0; --at_pos) { + const git_tree_entry *entry = entries->contents[at_pos - 1]; + if (homing_search_cmp(&ksearch, entry) > 0) + break; + } + + return at_pos; +} + +unsigned int git_tree_entrycount(git_tree *tree) { assert(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((long *)&entry->attr, buffer, &buffer, 8) < GIT_SUCCESS) - return git__throw(GIT_EOBJCORRUPTED, "Failed to parse tree. Can't parse attributes"); + if (git__strtol32(&tmp, buffer, &buffer, 8) < 0 || + !buffer || !valid_attributes(tmp)) + return tree_error("Failed to parse tree. Can't parse attributes"); - if (*buffer++ != ' ') { - error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse tree. Object it corrupted"); - break; - } + entry->attr = tmp; - if (memchr(buffer, 0, buffer_end - buffer) == NULL) { - 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) + return tree_error("Failed to parse tree. Object is corrupted"); entry->filename = git__strdup(buffer); entry->filename_len = strlen(buffer); @@ -171,11 +272,11 @@ static int tree_parse_buffer(git_tree *tree, const char *buffer, const char *buf buffer++; - git_oid_mkraw(&entry->oid, (const unsigned char *)buffer); + git_oid_fromraw(&entry->oid, (const unsigned char *)buffer); 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) @@ -184,100 +285,169 @@ int git_tree__parse(git_tree *tree, git_odb_object *obj) return tree_parse_buffer(tree, (char *)obj->raw.data, (char *)obj->raw.data + obj->raw.len); } -static int write_index_entry(char *buffer, int mode, const char *path, size_t path_len, const git_oid *oid) +static unsigned int find_next_dir(const char *dirname, git_index *index, unsigned int start) { - int written; - written = sprintf(buffer, "%o %.*s%c", mode, (int)path_len, path, 0); - memcpy(buffer + written, &oid->id, GIT_OID_RAWSZ); - return written + GIT_OID_RAWSZ; + unsigned int i, entries = git_index_entrycount(index); + size_t dirlen; + + dirlen = strlen(dirname); + for (i = start; i < entries; ++i) { + git_index_entry *entry = git_index_get(index, i); + if (strlen(entry->path) < dirlen || + memcmp(entry->path, dirname, dirlen) || + (dirlen > 0 && entry->path[dirlen] != '/')) { + break; + } + } + + return i; } -static int write_index(git_oid *oid, git_index *index, const char *base, int baselen, int entry_no, int maxentries) +static int append_entry(git_treebuilder *bld, const char *filename, const git_oid *id, unsigned int attributes) { - size_t size, offset; - char *buffer; - int nr, error; + git_tree_entry *entry; - /* Guess at some random initial size */ - size = maxentries * 40; - buffer = git__malloc(size); - if (buffer == NULL) - return GIT_ENOMEM; - - offset = 0; - - for (nr = entry_no; nr < maxentries; ++nr) { - git_index_entry *entry = git_index_get(index, nr); + entry = git__calloc(1, sizeof(git_tree_entry)); + GITERR_CHECK_ALLOC(entry); - const char *pathname = entry->path, *filename, *dirname; - int pathlen = strlen(pathname), entrylen; + entry->filename = git__strdup(filename); + entry->filename_len = strlen(entry->filename); - unsigned int write_mode; - git_oid subtree_oid; - git_oid *write_oid; - - /* Did we hit the end of the directory? Return how many we wrote */ - if (baselen >= pathlen || memcmp(base, pathname, baselen) != 0) - break; - - /* Do we have _further_ subdirectories? */ - filename = pathname + baselen; - dirname = strchr(filename, '/'); + git_oid_cpy(&entry->oid, id); + entry->attr = attributes; - write_oid = &entry->oid; - write_mode = entry->mode; + if (git_vector_insert(&bld->entries, entry) < 0) + return -1; - if (dirname) { - int subdir_written; + return 0; +} + +static int write_tree( + git_oid *oid, + git_repository *repo, + git_index *index, + const char *dirname, + unsigned int start) +{ + git_treebuilder *bld = NULL; -#if 0 - if (entry->mode != S_IFDIR) { - free(buffer); - return GIT_EOBJCORRUPTED; + unsigned int i, entries = git_index_entrycount(index); + int error; + size_t dirname_len = strlen(dirname); + const git_tree_cache *cache; + + cache = git_tree_cache_get(index->tree, dirname); + if (cache != NULL && cache->entries >= 0){ + git_oid_cpy(oid, &cache->oid); + return find_next_dir(dirname, index, start); + } + + error = git_treebuilder_create(&bld, NULL); + if (bld == NULL) { + return -1; + } + + /* + * This loop is unfortunate, but necessary. The index doesn't have + * any directores, so we need to handle that manually, and we + * need to keep track of the current position. + */ + for (i = start; i < entries; ++i) { + git_index_entry *entry = git_index_get(index, i); + char *filename, *next_slash; + + /* + * If we've left our (sub)tree, exit the loop and return. The + * first check is an early out (and security for the + * third). The second check is a simple prefix comparison. The + * third check catches situations where there is a directory + * win32/sys and a file win32mmap.c. Without it, the following + * code believes there is a file win32/mmap.c + */ + if (strlen(entry->path) < dirname_len || + memcmp(entry->path, dirname, dirname_len) || + (dirname_len > 0 && entry->path[dirname_len] != '/')) { + break; + } + + filename = entry->path + dirname_len; + if (*filename == '/') + filename++; + next_slash = strchr(filename, '/'); + if (next_slash) { + git_oid sub_oid; + int written; + char *subdir, *last_comp; + + subdir = git__strndup(entry->path, next_slash - entry->path); + GITERR_CHECK_ALLOC(subdir); + + /* Write out the subtree */ + written = write_tree(&sub_oid, repo, index, subdir, i); + if (written < 0) { + tree_error("Failed to write subtree"); + goto on_error; + } else { + i = written - 1; /* -1 because of the loop increment */ } -#endif - subdir_written = write_index(&subtree_oid, index, pathname, dirname - pathname + 1, nr, maxentries); - if (subdir_written < GIT_SUCCESS) { - free(buffer); - return subdir_written; + /* + * We need to figure out what we want toinsert + * into this tree. If we're traversing + * deps/zlib/, then we only want to write + * 'zlib' into the tree. + */ + last_comp = strrchr(subdir, '/'); + if (last_comp) { + last_comp++; /* Get rid of the '/' */ + } else { + last_comp = subdir; + } + error = append_entry(bld, last_comp, &sub_oid, S_IFDIR); + git__free(subdir); + if (error < 0) { + tree_error("Failed to insert dir"); + goto on_error; + } + } else { + error = append_entry(bld, filename, &entry->oid, entry->mode); + if (error < 0) { + tree_error("Failed to insert file"); + goto on_error; } - - nr = subdir_written - 1; - - /* Now we need to write out the directory entry into this tree.. */ - pathlen = dirname - pathname; - write_oid = &subtree_oid; - write_mode = S_IFDIR; } + } - entrylen = pathlen - baselen; - if (offset + entrylen + 32 > size) { - size = alloc_nr(offset + entrylen + 32); - buffer = git__realloc(buffer, size); - - if (buffer == NULL) - return GIT_ENOMEM; - } + if (git_treebuilder_write(oid, repo, bld) < 0) + goto on_error; - offset += write_index_entry(buffer + offset, write_mode, filename, entrylen, write_oid); - } - - error = git_odb_write(oid, index->repository->db, buffer, offset, GIT_OBJ_TREE); - free(buffer); + git_treebuilder_free(bld); + return i; - return (error == GIT_SUCCESS) ? nr : git__rethrow(error, "Failed to write index"); +on_error: + git_treebuilder_free(bld); + return -1; } int git_tree_create_fromindex(git_oid *oid, git_index *index) { - int error; + int ret; + git_repository *repo; - if (index->repository == NULL) - return git__throw(GIT_EBAREINDEX, "Failed to create tree. The index file is not backed up by an existing repository"); + repo = (git_repository *)GIT_REFCOUNT_OWNER(index); - error = write_index(oid, index, "", 0, 0, git_index_entrycount(index)); - return (error < GIT_SUCCESS) ? git__rethrow(error, "Failed to create tree") : GIT_SUCCESS; + if (repo == NULL) + 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 0; + } + + /* The tree cache didn't help us */ + ret = write_tree(oid, repo, index, "", 0); + return ret < 0 ? ret : 0; } static void sort_entries(git_treebuilder *bld) @@ -288,51 +458,34 @@ static void sort_entries(git_treebuilder *bld) int git_treebuilder_create(git_treebuilder **builder_p, const git_tree *source) { git_treebuilder *bld; - size_t i, source_entries = DEFAULT_TREE_SIZE; + unsigned int i, source_entries = DEFAULT_TREE_SIZE; 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) { - free(bld); - return GIT_ENOMEM; - } + if (git_vector_init(&bld->entries, source_entries, entry_sort_cmp) < 0) + goto on_error; if (source != NULL) { - bld->entry_count = source_entries; for (i = 0; i < source->entries.length; ++i) { git_tree_entry *entry_src = source->entries.contents[i]; - git_tree_entry *entry = git__calloc(1, sizeof(git_tree_entry)); - - if (entry == NULL) { - git_treebuilder_free(bld); - return GIT_ENOMEM; - } - entry->filename = git__strdup(entry_src->filename); - - if (entry->filename == NULL) { - free(entry); - git_treebuilder_free(bld); - return GIT_ENOMEM; - } - - entry->filename_len = entry_src->filename_len; - git_oid_cpy(&entry->oid, &entry_src->oid); - entry->attr = entry_src->attr; - - git_vector_insert(&bld->entries, entry); + 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) @@ -343,23 +496,23 @@ 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 atrributes"); + return tree_error("Failed to insert entry. Invalid attributes"); - if ((pos = git_vector_bsearch2(&bld->entries, entry_search_cmp, filename)) != GIT_ENOTFOUND) { + if (!valid_entry_name(filename)) + return tree_error("Failed to insert entry. Invalid name for a tree entry"); + + pos = tree_key_search(&bld->entries, filename); + + if (pos >= 0) { entry = git_vector_get(&bld->entries, pos); - if (entry->removed) { + if (entry->removed) entry->removed = 0; - bld->entry_count++; - } } 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); - - bld->entry_count++; } git_oid_cpy(&entry->oid, id); @@ -367,25 +520,24 @@ 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; } -const git_tree_entry *git_treebuilder_get(git_treebuilder *bld, const char *filename) +static git_tree_entry *treebuilder_get(git_treebuilder *bld, const char *filename) { int idx; git_tree_entry *entry; assert(bld && filename); - sort_entries(bld); - idx = git_vector_bsearch2(&bld->entries, entry_search_cmp, filename); - if (idx == GIT_ENOTFOUND) + idx = tree_key_search(&bld->entries, filename); + if (idx < 0) return NULL; entry = git_vector_get(&bld->entries, idx); @@ -395,65 +547,67 @@ const git_tree_entry *git_treebuilder_get(git_treebuilder *bld, const char *file return entry; } +const git_tree_entry *git_treebuilder_get(git_treebuilder *bld, const char *filename) +{ + return treebuilder_get(bld, filename); +} + int git_treebuilder_remove(git_treebuilder *bld, const char *filename) { - git_tree_entry *remove_ptr = (git_tree_entry *)git_treebuilder_get(bld, 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; - bld->entry_count--; - return GIT_SUCCESS; + return 0; } int git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *bld) { - size_t i, size = 0; - char filemode[MAX_FILEMODE_BYTES + 1 + 1]; - git_odb_stream *stream; - int error; + unsigned int i; + git_buf tree = GIT_BUF_INIT; + git_odb *odb; assert(bld); sort_entries(bld); + /* Grow the buffer beforehand to an estimated size */ + git_buf_grow(&tree, bld->entries.length * 72); + for (i = 0; i < bld->entries.length; ++i) { git_tree_entry *entry = bld->entries.contents[i]; if (entry->removed) continue; - snprintf(filemode, sizeof(filemode), "%o ", entry->attr); - size += strlen(filemode); - size += entry->filename_len + 1; - size += GIT_OID_RAWSZ; + git_buf_printf(&tree, "%o ", entry->attr); + git_buf_put(&tree, entry->filename, entry->filename_len + 1); + git_buf_put(&tree, (char *)entry->oid.id, GIT_OID_RAWSZ); } - if ((error = git_odb_open_wstream(&stream, git_repository_database(repo), size, GIT_OBJ_TREE)) < GIT_SUCCESS) - return git__rethrow(error, "Failed to write tree. Can't open write stream"); + if (git_buf_oom(&tree)) + goto on_error; - for (i = 0; i < bld->entries.length; ++i) { - git_tree_entry *entry = bld->entries.contents[i]; + if (git_repository_odb__weakptr(&odb, repo) < 0) + goto on_error; - if (entry->removed) - continue; - snprintf(filemode, sizeof(filemode), "%o ", entry->attr); - stream->write(stream, filemode, strlen(filemode)); - stream->write(stream, entry->filename, entry->filename_len + 1); - stream->write(stream, (char *)entry->oid.id, GIT_OID_RAWSZ); - } + if (git_odb_write(oid, odb, tree.ptr, tree.size, GIT_OBJ_TREE) < 0) + goto on_error; - error = stream->finalize_write(oid, stream); - stream->free(stream); + 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) { - size_t i; + unsigned int i; assert(bld && filter); @@ -466,13 +620,13 @@ void git_treebuilder_filter(git_treebuilder *bld, int (*filter)(const git_tree_e void git_treebuilder_clear(git_treebuilder *bld) { - size_t i; + unsigned int i; assert(bld); for (i = 0; i < bld->entries.length; ++i) { git_tree_entry *e = bld->entries.contents[i]; - free(e->filename); - free(e); + git__free(e->filename); + git__free(e); } git_vector_clear(&bld->entries); @@ -482,7 +636,150 @@ void git_treebuilder_free(git_treebuilder *bld) { git_treebuilder_clear(bld); git_vector_free(&bld->entries); - free(bld); + git__free(bld); +} + +static int tree_frompath( + git_tree **parent_out, + git_tree *root, + git_buf *treeentry_path, + size_t offset) +{ + char *slash_pos = NULL; + const git_tree_entry* entry; + int error = 0; + git_tree *subtree; + + 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, '/'); + + if (slash_pos == NULL) + return git_tree_lookup( + parent_out, + root->object.repo, + git_object_id((const git_object *)root) + ); + + 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'; + + entry = git_tree_entry_byname(root, treeentry_path->ptr + offset); + + if (slash_pos != NULL) + *slash_pos = '/'; + + 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; + } + + + if (git_tree_lookup(&subtree, root->object.repo, &entry->oid) < 0) + return error; + + error = tree_frompath( + parent_out, + subtree, + treeentry_path, + (slash_pos - treeentry_path->ptr) + 1 + ); + + git_tree_free(subtree); + return error; +} + +int git_tree_get_subtree( + git_tree **subtree, + git_tree *root, + const char *subtree_path) +{ + int error; + git_buf buffer = GIT_BUF_INIT; + + assert(subtree && root && subtree_path); + + if ((error = git_buf_sets(&buffer, subtree_path)) == 0) + error = tree_frompath(subtree, root, &buffer, 0); + + git_buf_free(&buffer); + + return error; +} + +static int tree_walk_post( + git_tree *tree, + git_treewalk_cb callback, + git_buf *path, + void *payload) +{ + int error = 0; + unsigned int i; + + for (i = 0; i < tree->entries.length; ++i) { + git_tree_entry *entry = tree->entries.contents[i]; + + if (callback(path->ptr, entry, payload) < 0) + continue; + + if (git_tree_entry__is_tree(entry)) { + git_tree *subtree; + size_t path_len = git_buf_len(path); + + if ((error = git_tree_lookup( + &subtree, tree->object.repo, &entry->oid)) < 0) + break; + + /* append the next entry to the path */ + git_buf_puts(path, entry->filename); + git_buf_putc(path, '/'); + + 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 0; } +int git_tree_walk(git_tree *tree, git_treewalk_cb callback, int mode, void *payload) +{ + int error = 0; + git_buf root_path = GIT_BUF_INIT; + + switch (mode) { + case GIT_TREEWALK_POST: + error = tree_walk_post(tree, callback, &root_path, payload); + break; + + case GIT_TREEWALK_PRE: + tree_error("Preorder tree walking is still not implemented"); + return -1; + + default: + giterr_set(GITERR_INVALID, "Invalid walking mode for tree walk"); + return -1; + } + + git_buf_free(&root_path); + + return error; +} diff --git a/src/tree.h b/src/tree.h index bff3f8edb..498a90d66 100644 --- a/src/tree.h +++ b/src/tree.h @@ -1,3 +1,9 @@ +/* + * 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_tree_h__ #define INCLUDE_tree_h__ @@ -21,11 +27,25 @@ struct git_tree { struct git_treebuilder { git_vector entries; - size_t entry_count; }; +GIT_INLINE(bool) git_tree_entry__is_tree(const struct git_tree_entry *e) +{ + return (S_ISDIR(e->attr) && !S_ISGITLINK(e->attr)); +} + void git_tree__free(git_tree *tree); int git_tree__parse(git_tree *tree, git_odb_object *obj); +/** + * Lookup the first position in the tree with a given prefix. + * + * @param tree a previously loaded tree. + * @param prefix the beginning of a path to find in the tree. + * @return index of the first item at or after the given prefix. + */ +int git_tree__prefix_position(git_tree *tree, const char *prefix); + + #endif diff --git a/src/tsort.c b/src/tsort.c new file mode 100644 index 000000000..f54c21e50 --- /dev/null +++ b/src/tsort.c @@ -0,0 +1,367 @@ +/* + * 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" + +/** + * An array-of-pointers implementation of Python's Timsort + * Based on code by Christopher Swenson under the MIT license + * + * Copyright (c) 2010 Christopher Swenson + * Copyright (c) 2011 Vicent Marti + */ + +#ifndef MAX +# define MAX(x,y) (((x) > (y) ? (x) : (y))) +#endif + +#ifndef MIN +# define MIN(x,y) (((x) < (y) ? (x) : (y))) +#endif + +typedef int (*cmp_ptr_t)(const void *, const void *); + +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 = (int)size - 1; + c = r >> 1; + lx = dst[l]; + + /* check for beginning conditions */ + if (cmp(x, lx) < 0) + return 0; + + else if (cmp(x, lx) == 0) { + int i = 1; + while (cmp(x, dst[i]) == 0) + i++; + return i; + } + + /* guaranteed not to be >= rx */ + cx = dst[c]; + while (1) { + const int val = cmp(x, cx); + if (val < 0) { + if (c - l <= 1) return c; + r = c; + } else if (val > 0) { + if (r - c <= 1) return c + 1; + l = c; + lx = cx; + } else { + do { + cx = dst[++c]; + } while (cmp(x, cx) == 0); + return c; + } + c = l + ((r - l) >> 1); + cx = dst[c]; + } +} + +/* Binary insertion sort, but knowing that the first "start" entries are sorted. Used in timsort. */ +static void bisort(void **dst, size_t start, size_t size, cmp_ptr_t cmp) +{ + size_t i; + void *x; + int location; + + for (i = start; i < size; i++) { + int j; + /* If this entry is already correct, just move along */ + if (cmp(dst[i - 1], dst[i]) <= 0) + continue; + + /* 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 = (int)i - 1; j >= location; j--) { + dst[j + 1] = dst[j]; + } + dst[location] = x; + } +} + + +/* timsort implementation, based on timsort.txt */ +struct tsort_run { + ssize_t start; + ssize_t length; +}; + +struct tsort_store { + size_t alloc; + cmp_ptr_t cmp; + void **storage; +}; + +static void reverse_elements(void **dst, ssize_t start, ssize_t end) +{ + while (start < end) { + void *tmp = dst[start]; + dst[start] = dst[end]; + dst[end] = tmp; + + start++; + end--; + } +} + +static ssize_t count_run(void **dst, ssize_t start, ssize_t size, struct tsort_store *store) +{ + ssize_t curr = start + 2; + + if (size - start == 1) + return 1; + + if (start >= size - 2) { + if (store->cmp(dst[size - 2], dst[size - 1]) > 0) { + void *tmp = dst[size - 1]; + dst[size - 1] = dst[size - 2]; + dst[size - 2] = tmp; + } + + return 2; + } + + if (store->cmp(dst[start], dst[start + 1]) <= 0) { + while (curr < size - 1 && store->cmp(dst[curr - 1], dst[curr]) <= 0) + curr++; + + return curr - start; + } else { + while (curr < size - 1 && store->cmp(dst[curr - 1], dst[curr]) > 0) + curr++; + + /* reverse in-place */ + reverse_elements(dst, start, curr - 1); + return curr - start; + } +} + +static size_t compute_minrun(size_t n) +{ + int r = 0; + while (n >= 64) { + r |= n & 1; + n >>= 1; + } + return n + r; +} + +static int check_invariant(struct tsort_run *stack, ssize_t stack_curr) +{ + if (stack_curr < 2) + return 1; + + else if (stack_curr == 2) { + const ssize_t A = stack[stack_curr - 2].length; + const ssize_t B = stack[stack_curr - 1].length; + return (A > B); + } else { + 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)); + } +} + +static int resize(struct tsort_store *store, size_t new_size) +{ + if (store->alloc < new_size) { + void **tempstore = git__realloc(store->storage, new_size * sizeof(void *)); + + /** + * Do not propagate on OOM; this will abort the sort and + * leave the array unsorted, but no error code will be + * raised + */ + if (tempstore == NULL) + return -1; + + store->storage = tempstore; + store->alloc = new_size; + } + + return 0; +} + +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; + const ssize_t curr = stack[stack_curr - 2].start; + + void **storage; + ssize_t i, j, k; + + if (resize(store, MIN(A, B)) < 0) + return; + + storage = store->storage; + + /* left merge */ + if (A < B) { + memcpy(storage, &dst[curr], A * sizeof(void *)); + i = 0; + j = curr + A; + + for (k = curr; k < curr + A + B; k++) { + if ((i < A) && (j < curr + A + B)) { + if (store->cmp(storage[i], dst[j]) <= 0) + dst[k] = storage[i++]; + else + dst[k] = dst[j++]; + } else if (i < A) { + dst[k] = storage[i++]; + } else + dst[k] = dst[j++]; + } + } else { + memcpy(storage, &dst[curr + A], B * sizeof(void *)); + i = B - 1; + j = curr + A - 1; + + for (k = curr + A + B - 1; k >= curr; k--) { + if ((i >= 0) && (j >= curr)) { + if (store->cmp(dst[j], storage[i]) > 0) + dst[k] = dst[j--]; + else + dst[k] = storage[i--]; + } else if (i >= 0) + dst[k] = storage[i--]; + else + dst[k] = dst[j--]; + } + } +} + +static ssize_t collapse(void **dst, struct tsort_run *stack, ssize_t stack_curr, struct tsort_store *store, ssize_t size) +{ + ssize_t A, B, C; + + while (1) { + /* if the stack only has one thing on it, we are done with the collapse */ + if (stack_curr <= 1) + break; + + /* if this is the last merge, just do it */ + if ((stack_curr == 2) && (stack[0].length + stack[1].length == size)) { + merge(dst, stack, stack_curr, store); + stack[0].length += stack[1].length; + stack_curr--; + break; + } + + /* check if the invariant is off for a stack of 2 elements */ + else if ((stack_curr == 2) && (stack[0].length <= stack[1].length)) { + merge(dst, stack, stack_curr, store); + stack[0].length += stack[1].length; + stack_curr--; + break; + } + else if (stack_curr == 2) + break; + + A = stack[stack_curr - 3].length; + B = stack[stack_curr - 2].length; + C = stack[stack_curr - 1].length; + + /* check first invariant */ + if (A <= B + C) { + if (A < C) { + merge(dst, stack, stack_curr - 1, store); + stack[stack_curr - 3].length += stack[stack_curr - 2].length; + stack[stack_curr - 2] = stack[stack_curr - 1]; + stack_curr--; + } else { + merge(dst, stack, stack_curr, store); + stack[stack_curr - 2].length += stack[stack_curr - 1].length; + stack_curr--; + } + } else if (B <= C) { + merge(dst, stack, stack_curr, store); + stack[stack_curr - 2].length += stack[stack_curr - 1].length; + stack_curr--; + } else + break; + } + + return stack_curr; +} + +#define PUSH_NEXT() do {\ + len = count_run(dst, curr, size, store);\ + run = minrun;\ + if (run < minrun) run = minrun;\ + if (run > (ssize_t)size - curr) run = size - curr;\ + if (run > len) {\ + bisort(&dst[curr], len, run, cmp);\ + len = run;\ + }\ + run_stack[stack_curr].start = curr;\ + run_stack[stack_curr++].length = len;\ + curr += len;\ + if (curr == (ssize_t)size) {\ + /* finish up */ \ + while (stack_curr > 1) { \ + merge(dst, run_stack, stack_curr, store); \ + run_stack[stack_curr - 2].length += run_stack[stack_curr - 1].length; \ + stack_curr--; \ + } \ + if (store->storage != NULL) {\ + git__free(store->storage);\ + store->storage = NULL;\ + }\ + return;\ + }\ +}\ +while (0) + +void git__tsort(void **dst, size_t size, cmp_ptr_t cmp) +{ + struct tsort_store _store, *store = &_store; + struct tsort_run run_stack[128]; + + ssize_t stack_curr = 0; + ssize_t len, run; + ssize_t curr = 0; + ssize_t minrun; + + if (size < 64) { + bisort(dst, 1, size, cmp); + return; + } + + /* compute the minimum run length */ + minrun = (ssize_t)compute_minrun(size); + + /* temporary storage for merges */ + store->alloc = 0; + store->storage = NULL; + store->cmp = cmp; + + PUSH_NEXT(); + PUSH_NEXT(); + PUSH_NEXT(); + + while (1) { + if (!check_invariant(run_stack, stack_curr)) { + stack_curr = collapse(dst, run_stack, stack_curr, store, size); + continue; + } + + PUSH_NEXT(); + } +} diff --git a/src/unix/map.c b/src/unix/map.c index 1613152a0..65f4ac91c 100644 --- a/src/unix/map.c +++ b/src/unix/map.c @@ -1,3 +1,12 @@ +/* + * 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 <git2/common.h> + +#ifndef GIT_WIN32 #include "map.h" #ifndef __amigaos4__ @@ -5,19 +14,13 @@ #endif #include <errno.h> - -int git__mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset) +int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset) { #ifndef __amigaos4__ 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; @@ -26,40 +29,31 @@ int git__mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t o 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; #endif - return GIT_SUCCESS; + return 0; } -int git__munmap(git_map *map) +int p_munmap(git_map *map) { #ifndef __amigaos4__ assert(map != NULL); - - if (!map) - return git__throw(GIT_ERROR, "Failed to munmap. Map does not exist"); - munmap(map->data, map->len); #endif - return GIT_SUCCESS; + return 0; } +#endif diff --git a/src/unix/posix.h b/src/unix/posix.h new file mode 100644 index 000000000..48b492941 --- /dev/null +++ b/src/unix/posix.h @@ -0,0 +1,31 @@ +/* + * 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_posix__w32_h__ +#define INCLUDE_posix__w32_h__ + +#ifndef __sun +# include <fnmatch.h> +# define p_fnmatch(p, s, f) fnmatch(p, s, f) +#else +# include "compat/fnmatch.h" +#endif + +#include <stdio.h> + +#define p_lstat(p,b) lstat(p,b) +#define p_readlink(a, b, c) readlink(a, b, c) +#define p_link(o,n) link(o, n) +#define p_unlink(p) unlink(p) +#define p_mkdir(p,m) mkdir(p, m) +#define p_fsync(fd) fsync(fd) +#define p_realpath(p, po) realpath(p, po) +#define p_vsnprintf(b, c, f, a) vsnprintf(b, c, f, a) +#define p_snprintf(b, c, f, ...) snprintf(b, c, f, __VA_ARGS__) +#define p_mkstemp(p) mkstemp(p) +#define p_setenv(n,v,o) setenv(n,v,o) + +#endif diff --git a/src/util.c b/src/util.c index 560c40dbb..3093cd767 100644 --- a/src/util.c +++ b/src/util.c @@ -1,22 +1,69 @@ -#define GIT__NO_HIDE_MALLOC +/* + * 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 <git2.h> #include "common.h" #include <stdarg.h> #include <stdio.h> #include <ctype.h> +#include "posix.h" + +#ifdef _MSC_VER +# include <Shlwapi.h> +#endif + +void git_libgit2_version(int *major, int *minor, int *rev) +{ + *major = LIBGIT2_VER_MAJOR; + *minor = LIBGIT2_VER_MINOR; + *rev = LIBGIT2_VER_REVISION; +} void git_strarray_free(git_strarray *array) { size_t i; for (i = 0; i < array->count; ++i) - free(array->strings[i]); + git__free(array->strings[i]); + + 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; - free(array->strings); + 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__strtol32(long *result, const char *nptr, const char **endptr, int base) +int git__strtol64(int64_t *result, const char *nptr, const char **endptr, int base) { const char *p; - long n, nn; + int64_t n, nn; int c, ovfl, v, neg, ndig; p = nptr; @@ -28,7 +75,7 @@ int git__strtol32(long *result, const char *nptr, const char **endptr, int base) /* * White space */ - while (isspace(*p)) + while (git__isspace(*p)) p++; /* @@ -78,35 +125,46 @@ int git__strtol32(long *result, const char *nptr, const char **endptr, int base) } 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__fmt(char *buf, size_t buf_sz, const char *fmt, ...) +int git__strtol32(int32_t *result, const char *nptr, const char **endptr, int base) { - va_list va; - int r; - - va_start(va, fmt); - r = vsnprintf(buf, buf_sz, fmt, va); - va_end(va); - if (r < 0 || ((size_t) r) >= buf_sz) - return git__throw(GIT_ERROR, "Failed to format string"); - return r; + int error; + int32_t tmp_int; + int64_t tmp_long; + + if ((error = git__strtol64(&tmp_long, nptr, endptr, base)) < 0) + return error; + + tmp_int = tmp_long & 0xFFFFFFFF; + if (tmp_int != tmp_long) { + giterr_set(GITERR_INVALID, "Failed to convert. '%s' is too large", nptr); + return -1; + } + + *result = tmp_int; + + return error; } -void git__strntolower(char *str, int len) +void git__strntolower(char *str, size_t len) { - int i; + size_t i; for (i = 0; i < len; ++i) { str[i] = (char) tolower(str[i]); @@ -121,7 +179,7 @@ void git__strtolower(char *str) int git__prefixcmp(const char *str, const char *prefix) { for (;;) { - char p = *(prefix++), s; + unsigned char p = *(prefix++), s; if (!p) return 0; if ((s = *(str++)) != p) @@ -138,205 +196,6 @@ int git__suffixcmp(const char *str, const char *suffix) return strcmp(str + (a - b), suffix); } -/* - * Based on the Android implementation, BSD licensed. - * Check http://android.git.kernel.org/ - */ -int git__basename_r(char *buffer, size_t bufflen, const char *path) -{ - const char *endp, *startp; - int len, result; - - /* Empty or NULL string gets treated as "." */ - if (path == NULL || *path == '\0') { - startp = "."; - len = 1; - goto Exit; - } - - /* Strip trailing slashes */ - endp = path + strlen(path) - 1; - while (endp > path && *endp == '/') - endp--; - - /* All slashes becomes "/" */ - if (endp == path && *endp == '/') { - startp = "/"; - len = 1; - goto Exit; - } - - /* Find the start of the base */ - startp = endp; - while (startp > path && *(startp - 1) != '/') - startp--; - - len = endp - startp +1; - -Exit: - result = len; - if (buffer == NULL) { - return result; - } - if (len > (int)bufflen-1) { - len = (int)bufflen-1; - result = GIT_ENOMEM; - } - - if (len >= 0) { - memmove(buffer, startp, len); - buffer[len] = 0; - } - return result; -} - -/* - * Based on the Android implementation, BSD licensed. - * Check http://android.git.kernel.org/ - */ -int git__dirname_r(char *buffer, size_t bufflen, const char *path) -{ - const char *endp; - int result, len; - - /* Empty or NULL string gets treated as "." */ - if (path == NULL || *path == '\0') { - path = "."; - len = 1; - goto Exit; - } - - /* Strip trailing slashes */ - endp = path + strlen(path) - 1; - while (endp > path && *endp == '/') - endp--; - - /* Find the start of the dir */ - while (endp > path && *endp != '/') - endp--; - - /* Either the dir is "/" or there are no slashes */ - if (endp == path) { - path = (*endp == '/') ? "/" : "."; - len = 1; - goto Exit; - } - - do { - endp--; - } while (endp > path && *endp == '/'); - - len = endp - path +1; - -Exit: - result = len; - if (len+1 > GIT_PATH_MAX) { - return GIT_ENOMEM; - } - if (buffer == NULL) - return result; - - if (len > (int)bufflen-1) { - len = (int)bufflen-1; - result = GIT_ENOMEM; - } - - if (len >= 0) { - memmove(buffer, path, len); - buffer[len] = 0; - } - return result; -} - - -char *git__dirname(const char *path) -{ - char *dname = NULL; - int len; - - len = (path ? strlen(path) : 0) + 2; - dname = (char *)git__malloc(len); - if (dname == NULL) - return NULL; - - if (git__dirname_r(dname, len, path) < GIT_SUCCESS) { - free(dname); - return NULL; - } - - return dname; -} - -char *git__basename(const char *path) -{ - char *bname = NULL; - int len; - - len = (path ? strlen(path) : 0) + 2; - bname = (char *)git__malloc(len); - if (bname == NULL) - return NULL; - - if (git__basename_r(bname, len, path) < GIT_SUCCESS) { - free(bname); - return NULL; - } - - return bname; -} - - -const char *git__topdir(const char *path) -{ - size_t len; - int i; - - assert(path); - len = strlen(path); - - if (!len || path[len - 1] != '/') - return NULL; - - for (i = len - 2; i >= 0; --i) - if (path[i] == '/') - break; - - return &path[i + 1]; -} - -void git__joinpath_n(char *buffer_out, int count, ...) -{ - va_list ap; - int i; - char *buffer_start = buffer_out; - - va_start(ap, count); - for (i = 0; i < count; ++i) { - const char *path; - int len; - - path = va_arg(ap, const char *); - - assert((i == 0) || path != buffer_start); - - if (i > 0 && *path == '/' && buffer_out > buffer_start && buffer_out[-1] == '/') - path++; - - if (!*path) - continue; - - len = strlen(path); - memmove(buffer_out, path, len); - buffer_out = buffer_out + len; - - if (i < count - 1 && buffer_out[-1] != '/') - *buffer_out++ = '/'; - } - va_end(ap); - - *buffer_out = '\0'; -} - char *git__strtok(char **end, const char *sep) { char *ptr = *end; @@ -393,7 +252,7 @@ void git__hexdump(const char *buffer, size_t len) printf("%02X ", (unsigned char)*line & 0xFF); for (j = 0; j < (LINE_WIDTH - last_line); ++j) - printf(" "); + printf(" "); printf("| "); @@ -419,22 +278,22 @@ uint32_t git__hash(const void *key, int len, unsigned int seed) while(len >= 4) { uint32_t k = *(uint32_t *)data; - k *= m; - k ^= k >> r; - k *= m; - - h *= m; + k *= m; + k ^= k >> r; + k *= m; + + h *= m; h ^= k; data += 4; len -= 4; } - + switch(len) { case 3: h ^= data[2] << 16; case 2: h ^= data[1] << 8; case 1: h ^= data[0]; - h *= m; + h *= m; }; h ^= h >> 13; @@ -442,7 +301,7 @@ uint32_t git__hash(const void *key, int len, unsigned int seed) h ^= h >> 15; return h; -} +} #else /* Cross-platform version of Murmurhash3 @@ -455,13 +314,13 @@ uint32_t git__hash(const void *key, int len, uint32_t seed) { #define MURMUR_BLOCK() {\ - k1 *= c1; \ - k1 = git__rotl(k1,11);\ - k1 *= c2;\ - h1 ^= k1;\ - h1 = h1*3 + 0x52dce729;\ - c1 = c1*5 + 0x7b7d159c;\ - c2 = c2*5 + 0x6bce6396;\ + k1 *= c1; \ + k1 = git__rotl(k1,11);\ + k1 *= c2;\ + h1 ^= k1;\ + h1 = h1*3 + 0x52dce729;\ + c1 = c1*5 + 0x7b7d159c;\ + c2 = c2*5 + 0x6bce6396;\ } const uint8_t *data = (const uint8_t*)key; @@ -500,5 +359,79 @@ uint32_t git__hash(const void *key, int len, uint32_t seed) h1 ^= h1 >> 16; return h1; -} +} #endif + +/** + * A modified `bsearch` from the BSD glibc. + * + * Copyright (c) 1990 Regents of the University of California. + * All rights reserved. + */ +int git__bsearch( + void **array, + size_t array_len, + const void *key, + int (*compare)(const void *, const void *), + size_t *position) +{ + unsigned int lim; + int cmp = -1; + void **part, **base = array; + + for (lim = (unsigned int)array_len; lim != 0; lim >>= 1) { + part = base + (lim >> 1); + cmp = (*compare)(key, *part); + if (cmp == 0) { + base = part; + break; + } + if (cmp > 0) { /* key > p; take right partition */ + base = part + 1; + lim--; + } /* else take left partition */ + } + + if (position) + *position = (base - array); + + return (cmp == 0) ? 0 : -1; +} + +/** + * A strcmp wrapper + * + * We don't want direct pointers to the CRT on Windows, we may + * get stdcall conflicts. + */ +int git__strcmp_cb(const void *a, const void *b) +{ + const char *stra = (const char *)a; + const char *strb = (const char *)b; + + return strcmp(stra, strb); +} + +int git__parse_bool(int *out, const char *value) +{ + /* A missing value means true */ + if (value == NULL) { + *out = 1; + return 0; + } + + if (!strcasecmp(value, "true") || + !strcasecmp(value, "yes") || + !strcasecmp(value, "on")) { + *out = 1; + return 0; + } + if (!strcasecmp(value, "false") || + !strcasecmp(value, "no") || + !strcasecmp(value, "off")) { + *out = 0; + return 0; + } + + return -1; +} diff --git a/src/util.h b/src/util.h index 72e3a9c68..c6851ac7e 100644 --- a/src/util.h +++ b/src/util.h @@ -1,11 +1,20 @@ +/* + * 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_util_h__ #define INCLUDE_util_h__ #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) -#define bitsizeof(x) (CHAR_BIT * sizeof(x)) +#define bitsizeof(x) (CHAR_BIT * sizeof(x)) #define MSB(x, bits) ((x) & (~0ULL << (bitsizeof(x) - (bits)))) +#ifndef min +# define min(a,b) ((a) < (b) ? (a) : (b)) +#endif -/* +/* * Custom memory allocation wrappers * that set error code and error message * on allocation failure @@ -13,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; } @@ -43,12 +49,14 @@ GIT_INLINE(char *) git__strndup(const char *str, size_t n) if (n < length) length = n; - ptr = malloc(length + 1); - if (!ptr) - git__throw(GIT_ENOMEM, "Out of memory. Failed to duplicate string"); + ptr = (char*)malloc(length + 1); + if (!ptr) { + giterr_set_oom(); + return NULL; + } memcpy(ptr, str, length); - ptr[length] = 0; + ptr[length] = '\0'; return ptr; } @@ -56,75 +64,21 @@ 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; } -extern int git__fmt(char *, size_t, const char *, ...) - GIT_FORMAT_PRINTF(3, 4); +#define git__free(ptr) free(ptr) + extern int git__prefixcmp(const char *str, const char *prefix); extern int git__suffixcmp(const char *str, const char *suffix); -extern int git__strtol32(long *n, const char *buff, const char **end_buf, int base); - -/* - * The dirname() function shall take a pointer to a character string - * that contains a pathname, and return a pointer to a string that is a - * pathname of the parent directory of that file. Trailing '/' characters - * in the path are not counted as part of the path. - * - * If path does not contain a '/', then dirname() shall return a pointer to - * the string ".". If path is a null pointer or points to an empty string, - * dirname() shall return a pointer to the string "." . - * - * The `git__dirname` implementation is thread safe. The returned - * string must be manually free'd. - * - * The `git__dirname_r` implementation expects a string allocated - * by the user with big enough size. - */ -extern char *git__dirname(const char *path); -extern int git__dirname_r(char *buffer, size_t bufflen, const char *path); - -/* - * This function returns the basename of the file, which is the last - * part of its full name given by fname, with the drive letter and - * leading directories stripped off. For example, the basename of - * c:/foo/bar/file.ext is file.ext, and the basename of a:foo is foo. - * - * Trailing slashes and backslashes are significant: the basename of - * c:/foo/bar/ is an empty string after the rightmost slash. - * - * The `git__basename` implementation is thread safe. The returned - * string must be manually free'd. - * - * The `git__basename_r` implementation expects a string allocated - * by the user with big enough size. - */ -extern char *git__basename(const char *path); -extern int git__basename_r(char *buffer, size_t bufflen, const char *path); - -extern const char *git__topdir(const char *path); - -/** - * Join two paths together. Takes care of properly fixing the - * middle slashes and everything - * - * The paths are joined together into buffer_out; this is expected - * to be an user allocated buffer of `GIT_PATH_MAX` size - */ -extern void git__joinpath_n(char *buffer_out, int npath, ...); - -GIT_INLINE(void) git__joinpath(char *buffer_out, const char *path_a, const char *path_b) -{ - git__joinpath_n(buffer_out, 2, path_a, path_b); -} +extern int git__strtol32(int32_t *n, const char *buff, const char **end_buf, int base); +extern int git__strtol64(int64_t *n, const char *buff, const char **end_buf, int base); extern void git__hexdump(const char *buffer, size_t n); extern uint32_t git__hash(const void *key, int len, uint32_t seed); - /** @return true if p fits into the range of a size_t */ GIT_INLINE(int) git__is_sizet(git_off_t p) { @@ -141,30 +95,132 @@ GIT_INLINE(int) git__is_sizet(git_off_t p) extern char *git__strtok(char **end, const char *sep); -extern void git__strntolower(char *str, int len); +extern void git__strntolower(char *str, size_t len); extern void git__strtolower(char *str); -#define STRLEN(str) (sizeof(str) - 1) +GIT_INLINE(const char *) git__next_line(const char *s) +{ + while (*s && *s != '\n') s++; + while (*s == '\n' || *s == '\r') s++; + return s; +} + +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, + const void *key, + int (*compare)(const void *, const void *), + size_t *position); -#define GIT_OID_LINE_LENGTH(header) (STRLEN(header) + 1 + GIT_OID_HEXSZ + 1) +extern int git__strcmp_cb(const void *a, const void *b); + +typedef struct { + short refcount; + void *owner; +} git_refcount; + +typedef void (*git_refcount_freeptr)(void *r); + +#define GIT_REFCOUNT_INC(r) { \ + ((git_refcount *)(r))->refcount++; \ +} + +#define GIT_REFCOUNT_DEC(_r, do_free) { \ + git_refcount *r = (git_refcount *)(_r); \ + r->refcount--; \ + if (r->refcount <= 0 && r->owner == NULL) { do_free(_r); } \ +} + +#define GIT_REFCOUNT_OWN(r, o) { \ + ((git_refcount *)(r))->owner = o; \ +} + +#define GIT_REFCOUNT_OWNER(r) (((git_refcount *)(r))->owner) + +static signed char from_hex[] = { +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 00 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 10 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 20 */ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, /* 30 */ +-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 40 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 50 */ +-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 60 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 70 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 80 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 90 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* a0 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* b0 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* c0 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* d0 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* e0 */ +-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* f0 */ +}; + +GIT_INLINE(int) git__fromhex(char h) +{ + return from_hex[(unsigned char) h]; +} + +GIT_INLINE(int) git__ishex(const char *str) +{ + unsigned i; + for (i=0; i<strlen(str); i++) + if (git__fromhex(str[i]) < 0) + return 0; + 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; +} + +GIT_INLINE(bool) git__isupper(int c) +{ + return (c >= 'A' && c <= 'Z'); +} + +GIT_INLINE(bool) git__isalpha(int c) +{ + return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')); +} + +GIT_INLINE(bool) git__isspace(int c) +{ + return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v'); +} + +GIT_INLINE(bool) git__iswildcard(int c) +{ + return (c == '*' || c == '?' || c == '['); +} /* - * Realloc the buffer pointed at by variable 'x' so that it can hold - * at least 'nr' entries; the number of entries currently allocated - * is 'alloc', using the standard growing factor alloc_nr() macro. + * Parse a string value as a boolean, just like Core Git + * does. * - * DO NOT USE any expression with side-effect for 'x' or 'alloc'. + * Valid values for true are: 'true', 'yes', 'on' + * Valid values for false are: 'false', 'no', 'off' */ -#define alloc_nr(x) (((x)+16)*3/2) -#define ALLOC_GROW(x, nr, alloc) \ - do { \ - if ((nr) > alloc) { \ - if (alloc_nr(alloc) < (nr)) \ - alloc = (nr); \ - else \ - alloc = alloc_nr(alloc); \ - x = xrealloc((x), alloc * sizeof(*(x))); \ - } \ - } while (0) +extern int git__parse_bool(int *out, const char *value); #endif /* INCLUDE_util_h__ */ diff --git a/src/vector.c b/src/vector.c index 1ddc26e3e..6f9aacccf 100644 --- a/src/vector.c +++ b/src/vector.c @@ -1,26 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. + * 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" @@ -28,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) { @@ -36,18 +18,21 @@ static int resize_vector(git_vector *v) if (v->_alloc_size < minimum_size) v->_alloc_size = minimum_size; - v->contents = realloc(v->contents, v->_alloc_size * sizeof(void *)); - if (v->contents == NULL) - return GIT_ENOMEM; + v->contents = git__realloc(v->contents, v->_alloc_size * sizeof(void *)); + GITERR_CHECK_ALLOC(v->contents); - return GIT_SUCCESS; + return 0; } - void git_vector_free(git_vector *v) { assert(v); - free(v->contents); + + git__free(v->contents); + v->contents = NULL; + + v->length = 0; + v->_alloc_size = 0; } int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp) @@ -61,30 +46,63 @@ int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp v->_alloc_size = initial_size; v->_cmp = cmp; - + v->length = 0; 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 result; + size_t pos; + + assert(v && v->_cmp); + + if (!v->sorted) + git_vector_sort(v); + + if (v->length >= v->_alloc_size && + resize_vector(v) < 0) + return -1; + + /* 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 (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) { + memmove(v->contents + pos + 1, v->contents + pos, + (v->length - pos) * sizeof(void *)); + } + + v->contents[pos] = element; + v->length++; + return 0; } void git_vector_sort(git_vector *v) @@ -94,30 +112,36 @@ void git_vector_sort(git_vector *v) if (v->sorted || v->_cmp == NULL) return; - qsort(v->contents, v->length, sizeof(void *), v->_cmp); + git__tsort(v->contents, v->length, v->_cmp); v->sorted = 1; } -int git_vector_bsearch2(git_vector *v, git_vector_cmp key_lookup, const void *key) +int git_vector_bsearch3( + unsigned int *at_pos, + git_vector *v, + git_vector_cmp key_lookup, + const void *key) { - void **find; + int rval; + size_t pos; 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); - find = bsearch(key, v->contents, v->length, sizeof(void *), key_lookup); - if (find != NULL) - return (int)(find - v->contents); + rval = git__bsearch(v->contents, v->length, key, key_lookup, &pos); - return git__throw(GIT_ENOTFOUND, "Can't find element"); + if (at_pos != NULL) + *at_pos = (unsigned int)pos; + + return (rval >= 0) ? (int)pos : GIT_ENOTFOUND; } -int git_vector_search2(git_vector *v, git_vector_cmp key_lookup, const void *key) +int git_vector_search2( + git_vector *v, git_vector_cmp key_lookup, const void *key) { unsigned int i; @@ -128,7 +152,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) @@ -141,11 +165,6 @@ int git_vector_search(git_vector *v, const void *entry) return git_vector_search2(v, v->_cmp ? v->_cmp : strict_comparison, entry); } -int git_vector_bsearch(git_vector *v, const void *key) -{ - return git_vector_bsearch2(v, v->_cmp, key); -} - int git_vector_remove(git_vector *v, unsigned int idx) { unsigned int i; @@ -153,13 +172,39 @@ 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) +{ + if (v->length > 0) + v->length--; +} + +void git_vector_uniq(git_vector *v) +{ + git_vector_cmp cmp; + unsigned int i, j; + + if (v->length <= 1) + return; + + git_vector_sort(v); + cmp = v->_cmp ? v->_cmp : strict_comparison; + + for (i = 0, j = 1 ; j < v->length; ++j) + if (!cmp(v->contents[i], v->contents[j])) + v->contents[i] = v->contents[j]; + else + v->contents[++i] = v->contents[j]; + + v->length -= j - i - 1; } void git_vector_clear(git_vector *v) @@ -169,4 +214,14 @@ void git_vector_clear(git_vector *v) v->sorted = 1; } +void git_vector_swap(git_vector *a, git_vector *b) +{ + git_vector t; + + if (!a || !b || a == b) + return; + memcpy(&t, a, sizeof(t)); + memcpy(a, b, sizeof(t)); + memcpy(b, &t, sizeof(t)); +} diff --git a/src/vector.h b/src/vector.h index 256452ee5..9139db345 100644 --- a/src/vector.h +++ b/src/vector.h @@ -1,3 +1,9 @@ +/* + * 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_vector_h__ #define INCLUDE_vector_h__ @@ -13,24 +19,60 @@ typedef struct git_vector { int sorted; } git_vector; +#define GIT_VECTOR_INIT {0} + int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp); void git_vector_free(git_vector *v); void git_vector_clear(git_vector *v); +void git_vector_swap(git_vector *a, git_vector *b); + +void git_vector_sort(git_vector *v); int git_vector_search(git_vector *v, const void *entry); int git_vector_search2(git_vector *v, git_vector_cmp cmp, const void *key); -int git_vector_bsearch(git_vector *v, const void *entry); -int git_vector_bsearch2(git_vector *v, git_vector_cmp cmp, const void *key); +int git_vector_bsearch3( + unsigned int *at_pos, git_vector *v, git_vector_cmp cmp, const void *key); -void git_vector_sort(git_vector *v); +GIT_INLINE(int) git_vector_bsearch(git_vector *v, const void *key) +{ + return git_vector_bsearch3(NULL, v, v->_cmp, key); +} + +GIT_INLINE(int) git_vector_bsearch2( + git_vector *v, git_vector_cmp cmp, const void *key) +{ + return git_vector_bsearch3(NULL, v, cmp, key); +} GIT_INLINE(void *) git_vector_get(git_vector *v, unsigned int position) { return (position < v->length) ? v->contents[position] : NULL; } +GIT_INLINE(const void *) git_vector_get_const(const git_vector *v, unsigned int position) +{ + 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; +} + +#define git_vector_foreach(v, iter, elem) \ + for ((iter) = 0; (iter) < (v)->length && ((elem) = (v)->contents[(iter)], 1); (iter)++ ) + +#define git_vector_rforeach(v, iter, elem) \ + for ((iter) = (v)->length; (iter) > 0 && ((elem) = (v)->contents[(iter)-1], 1); (iter)-- ) + int git_vector_insert(git_vector *v, void *element); +int git_vector_insert_sorted(git_vector *v, void *element, + int (*on_dup)(void **old, void *new)); int git_vector_remove(git_vector *v, unsigned int idx); +void git_vector_pop(git_vector *v); +void git_vector_uniq(git_vector *v); #endif diff --git a/src/win32/dir.c b/src/win32/dir.c index 069a41c3a..bc3d40fa5 100644 --- a/src/win32/dir.c +++ b/src/win32/dir.c @@ -1,5 +1,13 @@ +/* + * 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. + */ #define GIT__WIN32_NO_WRAP_DIR #include "dir.h" +#include "utf-conv.h" +#include "git2/windows.h" static int init_filter(char *filter, size_t n, const char *dir) { @@ -19,7 +27,8 @@ static int init_filter(char *filter, size_t n, const char *dir) git__DIR *git__opendir(const char *dir) { char filter[4096]; - git__DIR *new; + wchar_t* filter_w = NULL; + git__DIR *new = NULL; if (!dir || !init_filter(filter, sizeof(filter), dir)) return NULL; @@ -28,71 +37,119 @@ git__DIR *git__opendir(const char *dir) if (!new) return NULL; - new->dir = git__malloc(strlen(dir)+1); - if (!new->dir) { - 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); - new->h = FindFirstFile(filter, &new->f); if (new->h == INVALID_HANDLE_VALUE) { - free(new->dir); - 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; } -struct git__dirent *git__readdir(git__DIR *d) +int git__readdir_ext( + git__DIR *d, + struct git__dirent *entry, + struct git__dirent **result, + int *is_dir) { - if (!d || d->h == INVALID_HANDLE_VALUE) - return NULL; + if (!d || !entry || !result || d->h == INVALID_HANDLE_VALUE) + return -1; + + *result = NULL; if (d->first) d->first = 0; - else { - if (!FindNextFile(d->h, &d->f)) - return NULL; + else if (!FindNextFileW(d->h, &d->f)) { + if (GetLastError() == ERROR_NO_MORE_FILES) + return 0; + giterr_set(GITERR_OS, "Could not read from directory '%s'", d->dir); + return -1; } - if (strlen(d->f.cFileName) >= sizeof(d->entry.d_name)) - return NULL; + if (wcslen(d->f.cFileName) >= sizeof(entry->d_name)) + return -1; + + entry->d_ino = 0; + + if (WideCharToMultiByte( + gitwin_get_codepage(), 0, d->f.cFileName, -1, + entry->d_name, GIT_PATH_MAX, NULL, NULL) == 0) + { + giterr_set(GITERR_OS, "Could not convert filename to UTF-8"); + return -1; + } - d->entry.d_ino = 0; - strcpy(d->entry.d_name, d->f.cFileName); + *result = entry; - return &d->entry; + if (is_dir != NULL) + *is_dir = ((d->f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0); + + return 0; +} + +struct git__dirent *git__readdir(git__DIR *d) +{ + struct git__dirent *result; + if (git__readdir_ext(d, &d->entry, &result, NULL) < 0) + return NULL; + return result; } 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)) { - d->h = FindFirstFile(filter, &d->f); - if (d->h != INVALID_HANDLE_VALUE) - d->first = 1; - } } + + if (!init_filter(filter, sizeof(filter), d->dir) || + (filter_w = gitwin_to_utf16(filter)) == NULL) + return; + + 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) - free(d->dir); - 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/dir.h b/src/win32/dir.h new file mode 100644 index 000000000..c816d79bb --- /dev/null +++ b/src/win32/dir.h @@ -0,0 +1,42 @@ +/* + * 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_dir_h__ +#define INCLUDE_dir_h__ + +#include "common.h" + +struct git__dirent { + int d_ino; + char d_name[261]; +}; + +typedef struct { + HANDLE h; + WIN32_FIND_DATAW f; + struct git__dirent entry; + char *dir; + int first; +} git__DIR; + +extern git__DIR *git__opendir(const char *); +extern struct git__dirent *git__readdir(git__DIR *); +extern int git__readdir_ext( + git__DIR *, struct git__dirent *, struct git__dirent **, int *); +extern void git__rewinddir(git__DIR *); +extern int git__closedir(git__DIR *); + +# ifndef GIT__WIN32_NO_WRAP_DIR +# define dirent git__dirent +# define DIR git__DIR +# define opendir git__opendir +# define readdir git__readdir +# define readdir_r(d,e,r) git__readdir_ext((d),(e),(r),NULL) +# define rewinddir git__rewinddir +# define closedir git__closedir +# endif + +#endif /* INCLUDE_dir_h__ */ diff --git a/src/win32/fileops.c b/src/win32/fileops.c deleted file mode 100644 index d435e706e..000000000 --- a/src/win32/fileops.c +++ /dev/null @@ -1,41 +0,0 @@ -#define GIT__WIN32_NO_HIDE_FILEOPS -#include "fileops.h" -#include <errno.h> - -int git__unlink(const char *path) -{ - chmod(path, 0666); - return unlink(path); -} - -int git__mkstemp(char *template) -{ - char *file = mktemp(template); - if (file == NULL) - return -1; - return open(file, O_RDWR | O_CREAT | O_BINARY, 0600); -} - -int git__fsync(int fd) -{ - HANDLE fh = (HANDLE)_get_osfhandle(fd); - - if (fh == INVALID_HANDLE_VALUE) { - errno = EBADF; - return -1; - } - - if (!FlushFileBuffers(fh)) { - DWORD code = GetLastError(); - - if (code == ERROR_INVALID_HANDLE) - errno = EINVAL; - else - errno = EIO; - - return -1; - } - - return 0; -} - diff --git a/src/win32/git2.rc b/src/win32/git2.rc new file mode 100644 index 000000000..3a65c0a0f --- /dev/null +++ b/src/win32/git2.rc @@ -0,0 +1,42 @@ +#include <winver.h> +#include "../../include/git2/version.h" + +#ifndef INCLUDE_LIB +#define LIBGIT2_FILENAME "git2.dll" +#else +#define LIBGIT2_FILENAME "libgit2.dll" +#endif + +VS_VERSION_INFO VERSIONINFO MOVEABLE IMPURE LOADONCALL DISCARDABLE + FILEVERSION LIBGIT2_VER_MAJOR,LIBGIT2_VER_MINOR,LIBGIT2_VER_REVISION,0 + PRODUCTVERSION LIBGIT2_VER_MAJOR,LIBGIT2_VER_MINOR,LIBGIT2_VER_REVISION,0 + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS 1 +#else + FILEFLAGS 0 +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0 // not used +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + //language ID = U.S. English, char set = Windows, Multilingual + BEGIN + VALUE "FileDescription", "libgit2 - the Git linkable library\0" + VALUE "FileVersion", LIBGIT2_VERSION "\0" + VALUE "InternalName", LIBGIT2_FILENAME "\0" + VALUE "LegalCopyright", "Copyright (C) 2009-2012 the libgit2 contributors\0" + VALUE "OriginalFilename", LIBGIT2_FILENAME "\0" + VALUE "ProductName", "libgit2\0" + VALUE "ProductVersion", LIBGIT2_VERSION "\0" + VALUE "Comments", "For more information visit http://libgit2.github.com/\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 1252 + END +END diff --git a/src/win32/map.c b/src/win32/map.c index c7a39fcf6..f730120cc 100644 --- a/src/win32/map.c +++ b/src/win32/map.c @@ -1,3 +1,9 @@ +/* + * 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 "map.h" #include <errno.h> @@ -16,23 +22,18 @@ static DWORD get_page_size(void) return page_size; } -int git__mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset) +int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset) { HANDLE fh = (HANDLE)_get_osfhandle(fd); DWORD page_size = get_page_size(); DWORD fmap_prot = 0; DWORD view_prot = 0; DWORD off_low = 0; - DWORD off_hi = 0; + DWORD off_hi = 0; 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; @@ -40,86 +41,75 @@ int git__mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t o 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 */ + 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 git__munmap(git_map *map) +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/mingw-compat.h b/src/win32/mingw-compat.h new file mode 100644 index 000000000..6200dc094 --- /dev/null +++ b/src/win32/mingw-compat.h @@ -0,0 +1,24 @@ +/* + * 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_mingw_compat__ +#define INCLUDE_mingw_compat__ + +#if defined(__MINGW32__) + +/* use a 64-bit file offset type */ +# define lseek _lseeki64 +# define stat _stati64 +# define fstat _fstati64 + +/* stat: file mode type testing macros */ +# define _S_IFLNK 0120000 +# define S_IFLNK _S_IFLNK +# define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK) + +#endif + +#endif /* INCLUDE_mingw_compat__ */ diff --git a/src/win32/msvc-compat.h b/src/win32/msvc-compat.h new file mode 100644 index 000000000..ccc091cd0 --- /dev/null +++ b/src/win32/msvc-compat.h @@ -0,0 +1,42 @@ +/* + * 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_msvc_compat__ +#define INCLUDE_msvc_compat__ + +#if defined(_MSC_VER) + +/* access() mode parameter #defines */ +# define F_OK 0 /* existence check */ +# define W_OK 2 /* write mode check */ +# define R_OK 4 /* read mode check */ + +# define lseek _lseeki64 +# define stat _stat64 +# define fstat _fstat64 + +/* stat: file mode type testing macros */ +# define _S_IFLNK 0120000 +# define S_IFLNK _S_IFLNK +# define S_IXUSR 00100 + +# define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR) +# define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG) +# define S_ISFIFO(m) (((m) & _S_IFMT) == _S_IFIFO) +# define S_ISLNK(m) (((m) & _S_IFMT) == _S_IFLNK) + +# define mode_t unsigned short + +/* case-insensitive string comparison */ +# define strcasecmp _stricmp +# define strncasecmp _strnicmp + +/* MSVC doesn't define ssize_t at all */ +typedef SSIZE_T ssize_t; + +#endif + +#endif /* INCLUDE_msvc_compat__ */ diff --git a/src/win32/posix.h b/src/win32/posix.h new file mode 100644 index 000000000..baa4a3b4e --- /dev/null +++ b/src/win32/posix.h @@ -0,0 +1,55 @@ +/* + * 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_posix__w32_h__ +#define INCLUDE_posix__w32_h__ + +#include "common.h" +#include "compat/fnmatch.h" +#include "utf-conv.h" + +GIT_INLINE(int) p_link(const char *old, const char *new) +{ + GIT_UNUSED(old); + GIT_UNUSED(new); + errno = ENOSYS; + return -1; +} + +GIT_INLINE(int) p_mkdir(const char *path, mode_t mode) +{ + wchar_t* buf = gitwin_to_utf16(path); + int ret = _wmkdir(buf); + + GIT_UNUSED(mode); + + git__free(buf); + return ret; +} + +extern int p_unlink(const char *path); +extern int p_lstat(const char *file_name, struct stat *buf); +extern int p_readlink(const char *link, char *target, size_t target_len); +extern int p_hide_directory__w32(const char *path); +extern char *p_realpath(const char *orig_path, char *buffer); +extern int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr); +extern int p_snprintf(char *buffer, size_t count, const char *format, ...) GIT_FORMAT_PRINTF(3, 4); +extern int p_mkstemp(char *tmp_path); +extern int p_setenv(const char* name, const char* value, int overwrite); +extern int p_stat(const char* path, struct stat* buf); +extern int p_chdir(const char* path); +extern int p_chmod(const char* path, mode_t mode); +extern int p_rmdir(const char* path); +extern int p_access(const char* path, mode_t mode); +extern int p_fsync(int fd); +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 new file mode 100644 index 000000000..10de70da8 --- /dev/null +++ b/src/win32/posix_w32.c @@ -0,0 +1,472 @@ +/* + * 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 "../posix.h" +#include "path.h" +#include "utf-conv.h" +#include <errno.h> +#include <io.h> +#include <fcntl.h> + + +int p_unlink(const char *path) +{ + int ret = 0; + wchar_t* buf; + + if ((buf = gitwin_to_utf16(path)) != NULL) { + _wchmod(buf, 0666); + ret = _wunlink(buf); + git__free(buf); + } + + return ret; +} + +int p_fsync(int fd) +{ + HANDLE fh = (HANDLE)_get_osfhandle(fd); + + if (fh == INVALID_HANDLE_VALUE) { + errno = EBADF; + return -1; + } + + if (!FlushFileBuffers(fh)) { + DWORD code = GetLastError(); + + if (code == ERROR_INVALID_HANDLE) + errno = EINVAL; + else + errno = EIO; + + return -1; + } + + return 0; +} + +GIT_INLINE(time_t) filetime_to_time_t(const FILETIME *ft) +{ + long long winTime = ((long long)ft->dwHighDateTime << 32) + ft->dwLowDateTime; + winTime -= 116444736000000000LL; /* Windows to Unix Epoch conversion */ + winTime /= 10000000; /* Nano to seconds resolution */ + return (time_t)winTime; +} + +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; + + if (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + fMode |= S_IFDIR; + else + fMode |= S_IFREG; + + if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) + fMode |= S_IWRITE; + + if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) + fMode |= S_IFLNK; + + buf->st_ino = 0; + buf->st_gid = 0; + buf->st_uid = 0; + buf->st_nlink = 1; + buf->st_mode = (mode_t)fMode; + buf->st_size = ((git_off_t)fdata.nFileSizeHigh << 32) + fdata.nFileSizeLow; + buf->st_dev = buf->st_rdev = (_getdrive() - 1); + buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime)); + buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime)); + buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime)); + + git__free(fbuf); + return 0; + } + + git__free(fbuf); + return -1; +} + +int p_lstat(const char *file_name, struct stat *buf) +{ + int error; + size_t namelen; + char *alt_name; + + if (do_lstat(file_name, buf) == 0) + return 0; + + /* if file_name ended in a '/', Windows returned ENOENT; + * try again without trailing slashes + */ + namelen = strlen(file_name); + if (namelen && file_name[namelen-1] != '/') + return -1; + + while (namelen && file_name[namelen-1] == '/') + --namelen; + + if (!namelen) + return -1; + + alt_name = git__strndup(file_name, namelen); + if (!alt_name) + return -1; + + error = do_lstat(alt_name, buf); + + git__free(alt_name); + return error; +} + +int p_readlink(const char *link, char *target, size_t target_len) +{ + typedef DWORD (WINAPI *fpath_func)(HANDLE, LPWSTR, DWORD, DWORD); + static fpath_func pGetFinalPath = NULL; + HANDLE hFile; + 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 + * it is not available in platforms older than Vista + */ + if (pGetFinalPath == NULL) { + HINSTANCE library = LoadLibrary("kernel32"); + + if (library != NULL) + pGetFinalPath = (fpath_func)GetProcAddress(library, "GetFinalPathNameByHandleW"); + + 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 + FILE_SHARE_READ, // share for reading + NULL, // default security + OPEN_EXISTING, // existing file only + FILE_FLAG_BACKUP_SEMANTICS, // normal file + NULL); // no attr. template + + git__free(link_w); + + 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, (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 (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; +} + +int p_open(const char *path, int flags, ...) +{ + int fd; + wchar_t* buf; + mode_t mode = 0; + + buf = gitwin_to_utf16(path); + if (!buf) + return -1; + + if (flags & O_CREAT) + { + va_list arg_list; + + va_start(arg_list, flags); + mode = va_arg(arg_list, mode_t); + va_end(arg_list); + } + + fd = _wopen(buf, flags | _O_BINARY, mode); + + git__free(buf); + return fd; +} + +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) +{ + 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); + + ret = WideCharToMultiByte( + CP_UTF8, 0, buf, -1, buffer_out, (int)size, NULL, NULL); + + git__free(buf); + return !ret ? -1 : 0; +} + +int p_stat(const char* path, struct stat* buf) +{ + return do_lstat(path, buf); +} + +int p_chdir(const char* path) +{ + wchar_t* buf = gitwin_to_utf16(path); + int ret; + if (!buf) + return -1; + ret = _wchdir(buf); + git__free(buf); + return ret; +} + +int p_chmod(const char* path, mode_t mode) +{ + wchar_t* buf = gitwin_to_utf16(path); + int ret; + if (!buf) + return -1; + ret = _wchmod(buf, mode); + git__free(buf); + return ret; +} + +int p_rmdir(const char* path) +{ + wchar_t* buf = gitwin_to_utf16(path); + int ret; + if (!buf) + return -1; + ret = _wrmdir(buf); + git__free(buf); + return ret; +} + +int p_hide_directory__w32(const char *path) +{ + int res; + wchar_t* buf = gitwin_to_utf16(path); + if (!buf) + return -1; + + res = SetFileAttributesW(buf, FILE_ATTRIBUTE_HIDDEN); + git__free(buf); + + return (res != 0) ? 0 : -1; /* MSDN states a "non zero" value indicates a success */ +} + +char *p_realpath(const char *orig_path, char *buffer) +{ + int ret, buffer_sz = 0; + 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); + + /* According to MSDN, a return value equals to zero means a failure. */ + if (ret == 0 || ret > GIT_PATH_MAX) { + buffer = NULL; + goto done; + } + + if (buffer == NULL) { + buffer_sz = WideCharToMultiByte(CP_UTF8, 0, buffer_w, -1, NULL, 0, NULL, NULL); + + if (!buffer_sz || + !(buffer = (char *)git__malloc(buffer_sz)) || + !WideCharToMultiByte(CP_UTF8, 0, buffer_w, -1, buffer, buffer_sz, NULL, NULL)) + { + git__free(buffer); + buffer = NULL; + goto done; + } + } else { + if (!WideCharToMultiByte(CP_UTF8, 0, buffer_w, -1, buffer, GIT_PATH_MAX, NULL, NULL)) { + buffer = NULL; + goto done; + } + } + + if (!git_path_exists(buffer)) + { + if (buffer_sz > 0) + git__free(buffer); + + buffer = NULL; + errno = ENOENT; + } + +done: + git__free(buffer_w); + if (buffer) + git_path_mkposix(buffer); + return buffer; +} + +int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr) +{ +#ifdef _MSC_VER + 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 +} + +int p_snprintf(char *buffer, size_t count, const char *format, ...) +{ + va_list va; + int r; + + va_start(va, format); + r = p_vsnprintf(buffer, count, format, va); + va_end(va); + + return r; +} + +extern int p_creat(const char *path, mode_t mode); + +int p_mkstemp(char *tmp_path) +{ +#if defined(_MSC_VER) + if (_mktemp_s(tmp_path, strlen(tmp_path) + 1) != 0) + return -1; +#else + if (_mktemp(tmp_path) == NULL) + return -1; +#endif + + return p_creat(tmp_path, 0744); +} + +int p_setenv(const char* name, const char* value, int overwrite) +{ + if (overwrite != 1) + return -1; + + 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); + + return ret; +} + +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; + + 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 41cf5b35b..3a186c8d9 100644 --- a/src/win32/pthread.c +++ b/src/win32/pthread.c @@ -1,85 +1,68 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - * - * Original code by Ramiro Polla (Public Domain) + * 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 "pthread.h" -int pthread_create(pthread_t *GIT_RESTRICT thread, - const pthread_attr_t *GIT_RESTRICT GIT_UNUSED(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_ARG(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"); + GIT_UNUSED(attr); + *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) { - int ret; - ret = WaitForSingleObject(thread, INFINITE); - if (ret && value_ptr) - GetExitCodeThread(thread, (void*) value_ptr); - return -(!!ret); + int ret; + ret = WaitForSingleObject(thread, INFINITE); + if (ret && value_ptr) + GetExitCodeThread(thread, (void*) value_ptr); + return -(!!ret); } int pthread_mutex_init(pthread_mutex_t *GIT_RESTRICT mutex, - const pthread_mutexattr_t *GIT_RESTRICT GIT_UNUSED(mutexattr)) + const pthread_mutexattr_t *GIT_RESTRICT mutexattr) { - GIT_UNUSED_ARG(mutexattr); - InitializeCriticalSection(mutex); - return 0; + GIT_UNUSED(mutexattr); + InitializeCriticalSection(mutex); + return 0; } int pthread_mutex_destroy(pthread_mutex_t *mutex) { - DeleteCriticalSection(mutex); - return 0; + DeleteCriticalSection(mutex); + return 0; } int pthread_mutex_lock(pthread_mutex_t *mutex) { - EnterCriticalSection(mutex); - return 0; + EnterCriticalSection(mutex); + return 0; } int pthread_mutex_unlock(pthread_mutex_t *mutex) { - LeaveCriticalSection(mutex); - return 0; + LeaveCriticalSection(mutex); + return 0; } int pthread_num_processors_np(void) { - DWORD_PTR p, s; - int n = 0; + DWORD_PTR p, s; + int n = 0; - if (GetProcessAffinityMask(GetCurrentProcess(), &p, &s)) - for (; p; p >>= 1) - n += p&1; + if (GetProcessAffinityMask(GetCurrentProcess(), &p, &s)) + for (; p; p >>= 1) + n += p&1; - return n ? n : 1; + return n ? n : 1; } diff --git a/src/win32/pthread.h b/src/win32/pthread.h index 10949f1eb..b194cbfa7 100644 --- a/src/win32/pthread.h +++ b/src/win32/pthread.h @@ -1,28 +1,8 @@ /* - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. + * Copyright (C) 2009-2012 the libgit2 contributors * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - * - * Original code by Ramiro Polla (Public Domain) + * 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 GIT_PTHREAD_H @@ -45,8 +25,8 @@ typedef HANDLE pthread_t; #define PTHREAD_MUTEX_INITIALIZER {(void*)-1}; int pthread_create(pthread_t *GIT_RESTRICT, - const pthread_attr_t *GIT_RESTRICT, - void *(*start_routine)(void*), void *__restrict); + const pthread_attr_t *GIT_RESTRICT, + void *(*start_routine)(void*), void *__restrict); int pthread_join(pthread_t, void **); diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c new file mode 100644 index 000000000..0a705c0ad --- /dev/null +++ b/src/win32/utf-conv.c @@ -0,0 +1,92 @@ +/* + * 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 "utf-conv.h" +#include "git2/windows.h" + +/* + * Default codepage value + */ +static int _active_codepage = CP_UTF8; + +void gitwin_set_codepage(unsigned int codepage) +{ + _active_codepage = codepage; +} + +unsigned int gitwin_get_codepage(void) +{ + return _active_codepage; +} + +void gitwin_set_utf8(void) +{ + _active_codepage = CP_UTF8; +} + +wchar_t* gitwin_to_utf16(const char* str) +{ + wchar_t* ret; + int cb; + + if (!str) + return NULL; + + cb = MultiByteToWideChar(_active_codepage, 0, str, -1, NULL, 0); + if (cb == 0) + return (wchar_t *)git__calloc(1, sizeof(wchar_t)); + + ret = (wchar_t *)git__malloc(cb * sizeof(wchar_t)); + if (!ret) + return NULL; + + 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; + } + + return ret; +} + +int gitwin_append_utf16(wchar_t *buffer, const char *str, size_t 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; + + if (!str) + return NULL; + + cb = WideCharToMultiByte(_active_codepage, 0, str, -1, NULL, 0, NULL, NULL); + if (cb == 0) + return (char *)git__calloc(1, sizeof(char)); + + ret = (char*)git__malloc(cb); + if (!ret) + return NULL; + + 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; + } + + return ret; + +} diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h new file mode 100644 index 000000000..ae9f29f6c --- /dev/null +++ b/src/win32/utf-conv.h @@ -0,0 +1,18 @@ +/* + * 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 <wchar.h> + +#ifndef INCLUDE_git_utfconv_h__ +#define INCLUDE_git_utfconv_h__ + +wchar_t* gitwin_to_utf16(const char* str); +int gitwin_append_utf16(wchar_t *buffer, const char *str, size_t len); +char* gitwin_from_utf16(const wchar_t* str); + +#endif + diff --git a/src/xdiff/xdiff.h b/src/xdiff/xdiff.h new file mode 100644 index 000000000..cb8b235b5 --- /dev/null +++ b/src/xdiff/xdiff.h @@ -0,0 +1,135 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#if !defined(XDIFF_H) +#define XDIFF_H + +#ifdef __cplusplus +extern "C" { +#endif /* #ifdef __cplusplus */ + + +#define XDF_NEED_MINIMAL (1 << 1) +#define XDF_IGNORE_WHITESPACE (1 << 2) +#define XDF_IGNORE_WHITESPACE_CHANGE (1 << 3) +#define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 4) +#define XDF_PATIENCE_DIFF (1 << 5) +#define XDF_HISTOGRAM_DIFF (1 << 6) +#define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | XDF_IGNORE_WHITESPACE_CHANGE | XDF_IGNORE_WHITESPACE_AT_EOL) + +#define XDL_PATCH_NORMAL '-' +#define XDL_PATCH_REVERSE '+' +#define XDL_PATCH_MODEMASK ((1 << 8) - 1) +#define XDL_PATCH_IGNOREBSPACE (1 << 8) + +#define XDL_EMIT_FUNCNAMES (1 << 0) +#define XDL_EMIT_COMMON (1 << 1) +#define XDL_EMIT_FUNCCONTEXT (1 << 2) + +#define XDL_MMB_READONLY (1 << 0) + +#define XDL_MMF_ATOMIC (1 << 0) + +#define XDL_BDOP_INS 1 +#define XDL_BDOP_CPY 2 +#define XDL_BDOP_INSB 3 + +/* merge simplification levels */ +#define XDL_MERGE_MINIMAL 0 +#define XDL_MERGE_EAGER 1 +#define XDL_MERGE_ZEALOUS 2 +#define XDL_MERGE_ZEALOUS_ALNUM 3 + +/* merge favor modes */ +#define XDL_MERGE_FAVOR_OURS 1 +#define XDL_MERGE_FAVOR_THEIRS 2 +#define XDL_MERGE_FAVOR_UNION 3 + +/* merge output styles */ +#define XDL_MERGE_DIFF3 1 + +typedef struct s_mmfile { + char *ptr; + size_t size; +} mmfile_t; + +typedef struct s_mmbuffer { + char *ptr; + size_t size; +} mmbuffer_t; + +typedef struct s_xpparam { + unsigned long flags; +} xpparam_t; + +typedef struct s_xdemitcb { + void *priv; + int (*outf)(void *, mmbuffer_t *, int); +} xdemitcb_t; + +typedef long (*find_func_t)(const char *line, long line_len, char *buffer, long buffer_size, void *priv); + +typedef struct s_xdemitconf { + long ctxlen; + long interhunkctxlen; + unsigned long flags; + find_func_t find_func; + void *find_func_priv; + void (*emit_func)(void); +} xdemitconf_t; + +typedef struct s_bdiffparam { + long bsize; +} bdiffparam_t; + + +#define xdl_malloc(x) malloc(x) +#define xdl_free(ptr) free(ptr) +#define xdl_realloc(ptr,x) realloc(ptr,x) + +void *xdl_mmfile_first(mmfile_t *mmf, long *size); +long xdl_mmfile_size(mmfile_t *mmf); + +int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdemitconf_t const *xecfg, xdemitcb_t *ecb); + +typedef struct s_xmparam { + xpparam_t xpp; + int marker_size; + int level; + int favor; + int style; + const char *ancestor; /* label for orig */ + const char *file1; /* label for mf1 */ + const char *file2; /* label for mf2 */ +} xmparam_t; + +#define DEFAULT_CONFLICT_MARKER_SIZE 7 + +int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2, + xmparam_t const *xmp, mmbuffer_t *result); + +#ifdef __cplusplus +} +#endif /* #ifdef __cplusplus */ + +#endif /* #if !defined(XDIFF_H) */ diff --git a/src/xdiff/xdiffi.c b/src/xdiff/xdiffi.c new file mode 100644 index 000000000..75a392275 --- /dev/null +++ b/src/xdiff/xdiffi.c @@ -0,0 +1,572 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#include "xinclude.h" + + + +#define XDL_MAX_COST_MIN 256 +#define XDL_HEUR_MIN_COST 256 +#define XDL_LINE_MAX (long)((1UL << (CHAR_BIT * sizeof(long) - 1)) - 1) +#define XDL_SNAKE_CNT 20 +#define XDL_K_HEUR 4 + + + +typedef struct s_xdpsplit { + long i1, i2; + int min_lo, min_hi; +} xdpsplit_t; + + + + +static long xdl_split(unsigned long const *ha1, long off1, long lim1, + unsigned long const *ha2, long off2, long lim2, + long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl, + xdalgoenv_t *xenv); +static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, long chg2); + + + + + +/* + * See "An O(ND) Difference Algorithm and its Variations", by Eugene Myers. + * Basically considers a "box" (off1, off2, lim1, lim2) and scan from both + * the forward diagonal starting from (off1, off2) and the backward diagonal + * starting from (lim1, lim2). If the K values on the same diagonal crosses + * returns the furthest point of reach. We might end up having to expensive + * cases using this algorithm is full, so a little bit of heuristic is needed + * to cut the search and to return a suboptimal point. + */ +static long xdl_split(unsigned long const *ha1, long off1, long lim1, + unsigned long const *ha2, long off2, long lim2, + long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl, + xdalgoenv_t *xenv) { + long dmin = off1 - lim2, dmax = lim1 - off2; + long fmid = off1 - off2, bmid = lim1 - lim2; + long odd = (fmid - bmid) & 1; + long fmin = fmid, fmax = fmid; + long bmin = bmid, bmax = bmid; + long ec, d, i1, i2, prev1, best, dd, v, k; + + /* + * Set initial diagonal values for both forward and backward path. + */ + kvdf[fmid] = off1; + kvdb[bmid] = lim1; + + for (ec = 1;; ec++) { + int got_snake = 0; + + /* + * We need to extent the diagonal "domain" by one. If the next + * values exits the box boundaries we need to change it in the + * opposite direction because (max - min) must be a power of two. + * Also we initialize the external K value to -1 so that we can + * avoid extra conditions check inside the core loop. + */ + if (fmin > dmin) + kvdf[--fmin - 1] = -1; + else + ++fmin; + if (fmax < dmax) + kvdf[++fmax + 1] = -1; + else + --fmax; + + for (d = fmax; d >= fmin; d -= 2) { + if (kvdf[d - 1] >= kvdf[d + 1]) + i1 = kvdf[d - 1] + 1; + else + i1 = kvdf[d + 1]; + prev1 = i1; + i2 = i1 - d; + for (; i1 < lim1 && i2 < lim2 && ha1[i1] == ha2[i2]; i1++, i2++); + if (i1 - prev1 > xenv->snake_cnt) + got_snake = 1; + kvdf[d] = i1; + if (odd && bmin <= d && d <= bmax && kvdb[d] <= i1) { + spl->i1 = i1; + spl->i2 = i2; + spl->min_lo = spl->min_hi = 1; + return ec; + } + } + + /* + * We need to extent the diagonal "domain" by one. If the next + * values exits the box boundaries we need to change it in the + * opposite direction because (max - min) must be a power of two. + * Also we initialize the external K value to -1 so that we can + * avoid extra conditions check inside the core loop. + */ + if (bmin > dmin) + kvdb[--bmin - 1] = XDL_LINE_MAX; + else + ++bmin; + if (bmax < dmax) + kvdb[++bmax + 1] = XDL_LINE_MAX; + else + --bmax; + + for (d = bmax; d >= bmin; d -= 2) { + if (kvdb[d - 1] < kvdb[d + 1]) + i1 = kvdb[d - 1]; + else + i1 = kvdb[d + 1] - 1; + prev1 = i1; + i2 = i1 - d; + for (; i1 > off1 && i2 > off2 && ha1[i1 - 1] == ha2[i2 - 1]; i1--, i2--); + if (prev1 - i1 > xenv->snake_cnt) + got_snake = 1; + kvdb[d] = i1; + if (!odd && fmin <= d && d <= fmax && i1 <= kvdf[d]) { + spl->i1 = i1; + spl->i2 = i2; + spl->min_lo = spl->min_hi = 1; + return ec; + } + } + + if (need_min) + continue; + + /* + * If the edit cost is above the heuristic trigger and if + * we got a good snake, we sample current diagonals to see + * if some of the, have reached an "interesting" path. Our + * measure is a function of the distance from the diagonal + * corner (i1 + i2) penalized with the distance from the + * mid diagonal itself. If this value is above the current + * edit cost times a magic factor (XDL_K_HEUR) we consider + * it interesting. + */ + if (got_snake && ec > xenv->heur_min) { + for (best = 0, d = fmax; d >= fmin; d -= 2) { + dd = d > fmid ? d - fmid: fmid - d; + i1 = kvdf[d]; + i2 = i1 - d; + v = (i1 - off1) + (i2 - off2) - dd; + + if (v > XDL_K_HEUR * ec && v > best && + off1 + xenv->snake_cnt <= i1 && i1 < lim1 && + off2 + xenv->snake_cnt <= i2 && i2 < lim2) { + for (k = 1; ha1[i1 - k] == ha2[i2 - k]; k++) + if (k == xenv->snake_cnt) { + best = v; + spl->i1 = i1; + spl->i2 = i2; + break; + } + } + } + if (best > 0) { + spl->min_lo = 1; + spl->min_hi = 0; + return ec; + } + + for (best = 0, d = bmax; d >= bmin; d -= 2) { + dd = d > bmid ? d - bmid: bmid - d; + i1 = kvdb[d]; + i2 = i1 - d; + v = (lim1 - i1) + (lim2 - i2) - dd; + + if (v > XDL_K_HEUR * ec && v > best && + off1 < i1 && i1 <= lim1 - xenv->snake_cnt && + off2 < i2 && i2 <= lim2 - xenv->snake_cnt) { + for (k = 0; ha1[i1 + k] == ha2[i2 + k]; k++) + if (k == xenv->snake_cnt - 1) { + best = v; + spl->i1 = i1; + spl->i2 = i2; + break; + } + } + } + if (best > 0) { + spl->min_lo = 0; + spl->min_hi = 1; + return ec; + } + } + + /* + * Enough is enough. We spent too much time here and now we collect + * the furthest reaching path using the (i1 + i2) measure. + */ + if (ec >= xenv->mxcost) { + long fbest, fbest1, bbest, bbest1; + + fbest = fbest1 = -1; + for (d = fmax; d >= fmin; d -= 2) { + i1 = XDL_MIN(kvdf[d], lim1); + i2 = i1 - d; + if (lim2 < i2) + i1 = lim2 + d, i2 = lim2; + if (fbest < i1 + i2) { + fbest = i1 + i2; + fbest1 = i1; + } + } + + bbest = bbest1 = XDL_LINE_MAX; + for (d = bmax; d >= bmin; d -= 2) { + i1 = XDL_MAX(off1, kvdb[d]); + i2 = i1 - d; + if (i2 < off2) + i1 = off2 + d, i2 = off2; + if (i1 + i2 < bbest) { + bbest = i1 + i2; + bbest1 = i1; + } + } + + if ((lim1 + lim2) - bbest < fbest - (off1 + off2)) { + spl->i1 = fbest1; + spl->i2 = fbest - fbest1; + spl->min_lo = 1; + spl->min_hi = 0; + } else { + spl->i1 = bbest1; + spl->i2 = bbest - bbest1; + spl->min_lo = 0; + spl->min_hi = 1; + } + return ec; + } + } +} + + +/* + * Rule: "Divide et Impera". Recursively split the box in sub-boxes by calling + * the box splitting function. Note that the real job (marking changed lines) + * is done in the two boundary reaching checks. + */ +int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1, + diffdata_t *dd2, long off2, long lim2, + long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv) { + unsigned long const *ha1 = dd1->ha, *ha2 = dd2->ha; + + /* + * Shrink the box by walking through each diagonal snake (SW and NE). + */ + for (; off1 < lim1 && off2 < lim2 && ha1[off1] == ha2[off2]; off1++, off2++); + for (; off1 < lim1 && off2 < lim2 && ha1[lim1 - 1] == ha2[lim2 - 1]; lim1--, lim2--); + + /* + * If one dimension is empty, then all records on the other one must + * be obviously changed. + */ + if (off1 == lim1) { + char *rchg2 = dd2->rchg; + long *rindex2 = dd2->rindex; + + for (; off2 < lim2; off2++) + rchg2[rindex2[off2]] = 1; + } else if (off2 == lim2) { + char *rchg1 = dd1->rchg; + long *rindex1 = dd1->rindex; + + for (; off1 < lim1; off1++) + rchg1[rindex1[off1]] = 1; + } else { + xdpsplit_t spl; + spl.i1 = spl.i2 = 0; + + /* + * Divide ... + */ + if (xdl_split(ha1, off1, lim1, ha2, off2, lim2, kvdf, kvdb, + need_min, &spl, xenv) < 0) { + + return -1; + } + + /* + * ... et Impera. + */ + if (xdl_recs_cmp(dd1, off1, spl.i1, dd2, off2, spl.i2, + kvdf, kvdb, spl.min_lo, xenv) < 0 || + xdl_recs_cmp(dd1, spl.i1, lim1, dd2, spl.i2, lim2, + kvdf, kvdb, spl.min_hi, xenv) < 0) { + + return -1; + } + } + + return 0; +} + + +int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *xe) { + long ndiags; + long *kvd, *kvdf, *kvdb; + xdalgoenv_t xenv; + diffdata_t dd1, dd2; + + if (xpp->flags & XDF_PATIENCE_DIFF) + return xdl_do_patience_diff(mf1, mf2, xpp, xe); + + if (xpp->flags & XDF_HISTOGRAM_DIFF) + return xdl_do_histogram_diff(mf1, mf2, xpp, xe); + + if (xdl_prepare_env(mf1, mf2, xpp, xe) < 0) { + + return -1; + } + + /* + * Allocate and setup K vectors to be used by the differential algorithm. + * One is to store the forward path and one to store the backward path. + */ + ndiags = xe->xdf1.nreff + xe->xdf2.nreff + 3; + if (!(kvd = (long *) xdl_malloc((2 * ndiags + 2) * sizeof(long)))) { + + xdl_free_env(xe); + return -1; + } + kvdf = kvd; + kvdb = kvdf + ndiags; + kvdf += xe->xdf2.nreff + 1; + kvdb += xe->xdf2.nreff + 1; + + xenv.mxcost = xdl_bogosqrt(ndiags); + if (xenv.mxcost < XDL_MAX_COST_MIN) + xenv.mxcost = XDL_MAX_COST_MIN; + xenv.snake_cnt = XDL_SNAKE_CNT; + xenv.heur_min = XDL_HEUR_MIN_COST; + + dd1.nrec = xe->xdf1.nreff; + dd1.ha = xe->xdf1.ha; + dd1.rchg = xe->xdf1.rchg; + dd1.rindex = xe->xdf1.rindex; + dd2.nrec = xe->xdf2.nreff; + dd2.ha = xe->xdf2.ha; + dd2.rchg = xe->xdf2.rchg; + dd2.rindex = xe->xdf2.rindex; + + if (xdl_recs_cmp(&dd1, 0, dd1.nrec, &dd2, 0, dd2.nrec, + kvdf, kvdb, (xpp->flags & XDF_NEED_MINIMAL) != 0, &xenv) < 0) { + + xdl_free(kvd); + xdl_free_env(xe); + return -1; + } + + xdl_free(kvd); + + return 0; +} + + +static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, long chg2) { + xdchange_t *xch; + + if (!(xch = (xdchange_t *) xdl_malloc(sizeof(xdchange_t)))) + return NULL; + + xch->next = xscr; + xch->i1 = i1; + xch->i2 = i2; + xch->chg1 = chg1; + xch->chg2 = chg2; + + return xch; +} + + +int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { + long ix, ixo, ixs, ixref, grpsiz, nrec = xdf->nrec; + char *rchg = xdf->rchg, *rchgo = xdfo->rchg; + xrecord_t **recs = xdf->recs; + + /* + * This is the same of what GNU diff does. Move back and forward + * change groups for a consistent and pretty diff output. This also + * helps in finding joinable change groups and reduce the diff size. + */ + for (ix = ixo = 0;;) { + /* + * Find the first changed line in the to-be-compacted file. + * We need to keep track of both indexes, so if we find a + * changed lines group on the other file, while scanning the + * to-be-compacted file, we need to skip it properly. Note + * that loops that are testing for changed lines on rchg* do + * not need index bounding since the array is prepared with + * a zero at position -1 and N. + */ + for (; ix < nrec && !rchg[ix]; ix++) + while (rchgo[ixo++]); + if (ix == nrec) + break; + + /* + * Record the start of a changed-group in the to-be-compacted file + * and find the end of it, on both to-be-compacted and other file + * indexes (ix and ixo). + */ + ixs = ix; + for (ix++; rchg[ix]; ix++); + for (; rchgo[ixo]; ixo++); + + do { + grpsiz = ix - ixs; + + /* + * If the line before the current change group, is equal to + * the last line of the current change group, shift backward + * the group. + */ + while (ixs > 0 && recs[ixs - 1]->ha == recs[ix - 1]->ha && + xdl_recmatch(recs[ixs - 1]->ptr, recs[ixs - 1]->size, recs[ix - 1]->ptr, recs[ix - 1]->size, flags)) { + rchg[--ixs] = 1; + rchg[--ix] = 0; + + /* + * This change might have joined two change groups, + * so we try to take this scenario in account by moving + * the start index accordingly (and so the other-file + * end-of-group index). + */ + for (; rchg[ixs - 1]; ixs--); + while (rchgo[--ixo]); + } + + /* + * Record the end-of-group position in case we are matched + * with a group of changes in the other file (that is, the + * change record before the end-of-group index in the other + * file is set). + */ + ixref = rchgo[ixo - 1] ? ix: nrec; + + /* + * If the first line of the current change group, is equal to + * the line next of the current change group, shift forward + * the group. + */ + while (ix < nrec && recs[ixs]->ha == recs[ix]->ha && + xdl_recmatch(recs[ixs]->ptr, recs[ixs]->size, recs[ix]->ptr, recs[ix]->size, flags)) { + rchg[ixs++] = 0; + rchg[ix++] = 1; + + /* + * This change might have joined two change groups, + * so we try to take this scenario in account by moving + * the start index accordingly (and so the other-file + * end-of-group index). Keep tracking the reference + * index in case we are shifting together with a + * corresponding group of changes in the other file. + */ + for (; rchg[ix]; ix++); + while (rchgo[++ixo]) + ixref = ix; + } + } while (grpsiz != ix - ixs); + + /* + * Try to move back the possibly merged group of changes, to match + * the recorded postion in the other file. + */ + while (ixref < ix) { + rchg[--ixs] = 1; + rchg[--ix] = 0; + while (rchgo[--ixo]); + } + } + + return 0; +} + + +int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr) { + xdchange_t *cscr = NULL, *xch; + char *rchg1 = xe->xdf1.rchg, *rchg2 = xe->xdf2.rchg; + long i1, i2, l1, l2; + + /* + * Trivial. Collects "groups" of changes and creates an edit script. + */ + for (i1 = xe->xdf1.nrec, i2 = xe->xdf2.nrec; i1 >= 0 || i2 >= 0; i1--, i2--) + if (rchg1[i1 - 1] || rchg2[i2 - 1]) { + for (l1 = i1; rchg1[i1 - 1]; i1--); + for (l2 = i2; rchg2[i2 - 1]; i2--); + + if (!(xch = xdl_add_change(cscr, i1, i2, l1 - i1, l2 - i2))) { + xdl_free_script(cscr); + return -1; + } + cscr = xch; + } + + *xscr = cscr; + + return 0; +} + + +void xdl_free_script(xdchange_t *xscr) { + xdchange_t *xch; + + while ((xch = xscr) != NULL) { + xscr = xscr->next; + xdl_free(xch); + } +} + + +int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdemitconf_t const *xecfg, xdemitcb_t *ecb) { + xdchange_t *xscr; + xdfenv_t xe; + emit_func_t ef = xecfg->emit_func ? + (emit_func_t)xecfg->emit_func : xdl_emit_diff; + + if (xdl_do_diff(mf1, mf2, xpp, &xe) < 0) { + + return -1; + } + if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe.xdf2, &xe.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe, &xscr) < 0) { + + xdl_free_env(&xe); + return -1; + } + if (xscr) { + if (ef(&xe, xscr, ecb, xecfg) < 0) { + + xdl_free_script(xscr); + xdl_free_env(&xe); + return -1; + } + xdl_free_script(xscr); + } + xdl_free_env(&xe); + + return 0; +} diff --git a/src/xdiff/xdiffi.h b/src/xdiff/xdiffi.h new file mode 100644 index 000000000..7a92ea9c4 --- /dev/null +++ b/src/xdiff/xdiffi.h @@ -0,0 +1,63 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#if !defined(XDIFFI_H) +#define XDIFFI_H + + +typedef struct s_diffdata { + long nrec; + unsigned long const *ha; + long *rindex; + char *rchg; +} diffdata_t; + +typedef struct s_xdalgoenv { + long mxcost; + long snake_cnt; + long heur_min; +} xdalgoenv_t; + +typedef struct s_xdchange { + struct s_xdchange *next; + long i1, i2; + long chg1, chg2; +} xdchange_t; + + + +int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1, + diffdata_t *dd2, long off2, long lim2, + long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv); +int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *xe); +int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags); +int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr); +void xdl_free_script(xdchange_t *xscr); +int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, + xdemitconf_t const *xecfg); +int xdl_do_patience_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *env); +int xdl_do_histogram_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *env); + +#endif /* #if !defined(XDIFFI_H) */ diff --git a/src/xdiff/xemit.c b/src/xdiff/xemit.c new file mode 100644 index 000000000..e3e63d902 --- /dev/null +++ b/src/xdiff/xemit.c @@ -0,0 +1,253 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#include "xinclude.h" + + + + +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); + + + + +static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec) { + + *rec = xdf->recs[ri]->ptr; + + return xdf->recs[ri]->size; +} + + +static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t *ecb) { + long size, psize = (long)strlen(pre); + char const *rec; + + size = xdl_get_rec(xdf, ri, &rec); + if (xdl_emit_diffrec(rec, size, pre, psize, ecb) < 0) { + + return -1; + } + + return 0; +} + + +/* + * Starting at the passed change atom, find the latest change atom to be included + * inside the differential hunk according to the specified configuration. + */ +xdchange_t *xdl_get_hunk(xdchange_t *xscr, xdemitconf_t const *xecfg) { + xdchange_t *xch, *xchp; + long max_common = 2 * xecfg->ctxlen + xecfg->interhunkctxlen; + + for (xchp = xscr, xch = xscr->next; xch; xchp = xch, xch = xch->next) + if (xch->i1 - (xchp->i1 + xchp->chg1) > max_common) + break; + + return xchp; +} + + +static long def_ff(const char *rec, long len, char *buf, long sz, void *priv) +{ + (void)priv; + + if (len > 0 && + (isalpha((unsigned char)*rec) || /* identifier? */ + *rec == '_' || /* also identifier? */ + *rec == '$')) { /* identifiers from VMS and other esoterico */ + if (len > sz) + len = sz; + while (0 < len && isspace((unsigned char)rec[len - 1])) + len--; + memcpy(buf, rec, len); + return len; + } + return -1; +} + +static int xdl_emit_common(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, + xdemitconf_t const *xecfg) { + xdfile_t *xdf = &xe->xdf2; + const char *rchg = xdf->rchg; + long ix; + + (void)xscr; + (void)xecfg; + + for (ix = 0; ix < xdf->nrec; ix++) { + if (rchg[ix]) + continue; + if (xdl_emit_record(xdf, ix, "", ecb)) + return -1; + } + return 0; +} + +struct func_line { + long len; + char buf[80]; +}; + +static long get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg, + struct func_line *func_line, long start, long limit) +{ + find_func_t ff = xecfg->find_func ? xecfg->find_func : def_ff; + long l, size, step = (start > limit) ? -1 : 1; + char *buf, dummy[1]; + + buf = func_line ? func_line->buf : dummy; + size = func_line ? sizeof(func_line->buf) : sizeof(dummy); + + for (l = start; l != limit && 0 <= l && l < xe->xdf1.nrec; l += step) { + const char *rec; + long reclen = xdl_get_rec(&xe->xdf1, l, &rec); + long len = ff(rec, reclen, buf, size, xecfg->find_func_priv); + if (len >= 0) { + if (func_line) + func_line->len = len; + return l; + } + } + return -1; +} + +int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, + xdemitconf_t const *xecfg) { + long s1, s2, e1, e2, lctx; + xdchange_t *xch, *xche; + long funclineprev = -1; + struct func_line func_line = { 0 }; + + if (xecfg->flags & XDL_EMIT_COMMON) + return xdl_emit_common(xe, xscr, ecb, xecfg); + + for (xch = xscr; xch; xch = xche->next) { + xche = xdl_get_hunk(xch, xecfg); + + s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0); + s2 = XDL_MAX(xch->i2 - xecfg->ctxlen, 0); + + if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) { + long fs1 = get_func_line(xe, xecfg, NULL, xch->i1, -1); + if (fs1 < 0) + fs1 = 0; + if (fs1 < s1) { + s2 -= s1 - fs1; + s1 = fs1; + } + } + + again: + lctx = xecfg->ctxlen; + lctx = XDL_MIN(lctx, xe->xdf1.nrec - (xche->i1 + xche->chg1)); + lctx = XDL_MIN(lctx, xe->xdf2.nrec - (xche->i2 + xche->chg2)); + + e1 = xche->i1 + xche->chg1 + lctx; + e2 = xche->i2 + xche->chg2 + lctx; + + if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) { + long fe1 = get_func_line(xe, xecfg, NULL, + xche->i1 + xche->chg1, + xe->xdf1.nrec); + if (fe1 < 0) + fe1 = xe->xdf1.nrec; + if (fe1 > e1) { + e2 += fe1 - e1; + e1 = fe1; + } + + /* + * Overlap with next change? Then include it + * in the current hunk and start over to find + * its new end. + */ + if (xche->next) { + long l = xche->next->i1; + if (l <= e1 || + get_func_line(xe, xecfg, NULL, l, e1) < 0) { + xche = xche->next; + goto again; + } + } + } + + /* + * Emit current hunk header. + */ + + if (xecfg->flags & XDL_EMIT_FUNCNAMES) { + get_func_line(xe, xecfg, &func_line, + s1 - 1, funclineprev); + funclineprev = s1 - 1; + } + if (xdl_emit_hunk_hdr(s1 + 1, e1 - s1, s2 + 1, e2 - s2, + func_line.buf, func_line.len, ecb) < 0) + return -1; + + /* + * Emit pre-context. + */ + for (; s2 < xch->i2; s2++) + if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0) + return -1; + + for (s1 = xch->i1, s2 = xch->i2;; xch = xch->next) { + /* + * Merge previous with current change atom. + */ + for (; s1 < xch->i1 && s2 < xch->i2; s1++, s2++) + if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0) + return -1; + + /* + * Removes lines from the first file. + */ + for (s1 = xch->i1; s1 < xch->i1 + xch->chg1; s1++) + if (xdl_emit_record(&xe->xdf1, s1, "-", ecb) < 0) + return -1; + + /* + * Adds lines from the second file. + */ + for (s2 = xch->i2; s2 < xch->i2 + xch->chg2; s2++) + if (xdl_emit_record(&xe->xdf2, s2, "+", ecb) < 0) + return -1; + + if (xch == xche) + break; + s1 = xch->i1 + xch->chg1; + s2 = xch->i2 + xch->chg2; + } + + /* + * Emit post-context. + */ + for (s2 = xche->i2 + xche->chg2; s2 < e2; s2++) + if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0) + return -1; + } + + return 0; +} diff --git a/src/xdiff/xemit.h b/src/xdiff/xemit.h new file mode 100644 index 000000000..c2e2e8302 --- /dev/null +++ b/src/xdiff/xemit.h @@ -0,0 +1,36 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#if !defined(XEMIT_H) +#define XEMIT_H + + +typedef int (*emit_func_t)(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, + xdemitconf_t const *xecfg); + +xdchange_t *xdl_get_hunk(xdchange_t *xscr, xdemitconf_t const *xecfg); +int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, + xdemitconf_t const *xecfg); + + + +#endif /* #if !defined(XEMIT_H) */ diff --git a/src/xdiff/xhistogram.c b/src/xdiff/xhistogram.c new file mode 100644 index 000000000..5d101754d --- /dev/null +++ b/src/xdiff/xhistogram.c @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2010, Google Inc. + * and other copyright owners as documented in JGit's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "xinclude.h" +#include "xtypes.h" +#include "xdiff.h" + +#define MAX_PTR UINT_MAX +#define MAX_CNT UINT_MAX + +#define LINE_END(n) (line##n + count##n - 1) +#define LINE_END_PTR(n) (*line##n + *count##n - 1) + +struct histindex { + struct record { + unsigned int ptr, cnt; + struct record *next; + } **records, /* an ocurrence */ + **line_map; /* map of line to record chain */ + chastore_t rcha; + unsigned int *next_ptrs; + unsigned int table_bits, + records_size, + line_map_size; + + unsigned int max_chain_length, + key_shift, + ptr_shift; + + unsigned int cnt, + has_common; + + xdfenv_t *env; + xpparam_t const *xpp; +}; + +struct region { + unsigned int begin1, end1; + unsigned int begin2, end2; +}; + +#define LINE_MAP(i, a) (i->line_map[(a) - i->ptr_shift]) + +#define NEXT_PTR(index, ptr) \ + (index->next_ptrs[(ptr) - index->ptr_shift]) + +#define CNT(index, ptr) \ + ((LINE_MAP(index, ptr))->cnt) + +#define REC(env, s, l) \ + (env->xdf##s.recs[l - 1]) + +static int cmp_recs(xpparam_t const *xpp, + xrecord_t *r1, xrecord_t *r2) +{ + return r1->ha == r2->ha && + xdl_recmatch(r1->ptr, r1->size, r2->ptr, r2->size, + xpp->flags); +} + +#define CMP_ENV(xpp, env, s1, l1, s2, l2) \ + (cmp_recs(xpp, REC(env, s1, l1), REC(env, s2, l2))) + +#define CMP(i, s1, l1, s2, l2) \ + (cmp_recs(i->xpp, REC(i->env, s1, l1), REC(i->env, s2, l2))) + +#define TABLE_HASH(index, side, line) \ + XDL_HASHLONG((REC(index->env, side, line))->ha, index->table_bits) + +static int scanA(struct histindex *index, unsigned int line1, unsigned int count1) +{ + unsigned int ptr; + unsigned int tbl_idx; + unsigned int chain_len; + struct record **rec_chain, *rec; + + for (ptr = LINE_END(1); line1 <= ptr; ptr--) { + tbl_idx = TABLE_HASH(index, 1, ptr); + rec_chain = index->records + tbl_idx; + rec = *rec_chain; + + chain_len = 0; + while (rec) { + if (CMP(index, 1, rec->ptr, 1, ptr)) { + /* + * ptr is identical to another element. Insert + * it onto the front of the existing element + * chain. + */ + NEXT_PTR(index, ptr) = rec->ptr; + rec->ptr = ptr; + /* cap rec->cnt at MAX_CNT */ + rec->cnt = XDL_MIN(MAX_CNT, rec->cnt + 1); + LINE_MAP(index, ptr) = rec; + goto continue_scan; + } + + rec = rec->next; + chain_len++; + } + + if (chain_len == index->max_chain_length) + return -1; + + /* + * This is the first time we have ever seen this particular + * element in the sequence. Construct a new chain for it. + */ + if (!(rec = xdl_cha_alloc(&index->rcha))) + return -1; + rec->ptr = ptr; + rec->cnt = 1; + rec->next = *rec_chain; + *rec_chain = rec; + LINE_MAP(index, ptr) = rec; + +continue_scan: + ; /* no op */ + } + + return 0; +} + +static int try_lcs( + struct histindex *index, struct region *lcs, unsigned int b_ptr, + unsigned int line1, unsigned int count1, + unsigned int line2, unsigned int count2) +{ + unsigned int b_next = b_ptr + 1; + struct record *rec = index->records[TABLE_HASH(index, 2, b_ptr)]; + unsigned int as, ae, bs, be, np, rc; + int should_break; + + for (; rec; rec = rec->next) { + if (rec->cnt > index->cnt) { + if (!index->has_common) + index->has_common = CMP(index, 1, rec->ptr, 2, b_ptr); + continue; + } + + as = rec->ptr; + if (!CMP(index, 1, as, 2, b_ptr)) + continue; + + index->has_common = 1; + for (;;) { + should_break = 0; + np = NEXT_PTR(index, as); + bs = b_ptr; + ae = as; + be = bs; + rc = rec->cnt; + + while (line1 < as && line2 < bs + && CMP(index, 1, as - 1, 2, bs - 1)) { + as--; + bs--; + if (1 < rc) + rc = XDL_MIN(rc, CNT(index, as)); + } + while (ae < LINE_END(1) && be < LINE_END(2) + && CMP(index, 1, ae + 1, 2, be + 1)) { + ae++; + be++; + if (1 < rc) + rc = XDL_MIN(rc, CNT(index, ae)); + } + + if (b_next <= be) + b_next = be + 1; + if (lcs->end1 - lcs->begin1 < ae - as || rc < index->cnt) { + lcs->begin1 = as; + lcs->begin2 = bs; + lcs->end1 = ae; + lcs->end2 = be; + index->cnt = rc; + } + + if (np == 0) + break; + + while (np <= ae) { + np = NEXT_PTR(index, np); + if (np == 0) { + should_break = 1; + break; + } + } + + if (should_break) + break; + + as = np; + } + } + return b_next; +} + +static int find_lcs( + struct histindex *index, struct region *lcs, + unsigned int line1, unsigned int count1, + unsigned int line2, unsigned int count2) +{ + unsigned int b_ptr; + + if (scanA(index, line1, count1)) + return -1; + + index->cnt = index->max_chain_length + 1; + + for (b_ptr = line2; b_ptr <= LINE_END(2); ) + b_ptr = try_lcs(index, lcs, b_ptr, line1, count1, line2, count2); + + return index->has_common && index->max_chain_length < index->cnt; +} + +static int fall_back_to_classic_diff(struct histindex *index, + int line1, int count1, int line2, int count2) +{ + xpparam_t xpp; + xpp.flags = index->xpp->flags & ~XDF_HISTOGRAM_DIFF; + + return xdl_fall_back_diff(index->env, &xpp, + line1, count1, line2, count2); +} + +static int histogram_diff( + xpparam_t const *xpp, xdfenv_t *env, + unsigned int line1, unsigned int count1, + unsigned int line2, unsigned int count2) +{ + struct histindex index; + struct region lcs; + unsigned int sz; + int result = -1; + + if (count1 <= 0 && count2 <= 0) + return 0; + + if (LINE_END(1) >= MAX_PTR) + return -1; + + if (!count1) { + while(count2--) + env->xdf2.rchg[line2++ - 1] = 1; + return 0; + } else if (!count2) { + while(count1--) + env->xdf1.rchg[line1++ - 1] = 1; + return 0; + } + + memset(&index, 0, sizeof(index)); + + index.env = env; + index.xpp = xpp; + + index.records = NULL; + index.line_map = NULL; + /* in case of early xdl_cha_free() */ + index.rcha.head = NULL; + + index.table_bits = xdl_hashbits(count1); + sz = index.records_size = 1 << index.table_bits; + sz *= sizeof(struct record *); + if (!(index.records = (struct record **) xdl_malloc(sz))) + goto cleanup; + memset(index.records, 0, sz); + + sz = index.line_map_size = count1; + sz *= sizeof(struct record *); + if (!(index.line_map = (struct record **) xdl_malloc(sz))) + goto cleanup; + memset(index.line_map, 0, sz); + + sz = index.line_map_size; + sz *= sizeof(unsigned int); + if (!(index.next_ptrs = (unsigned int *) xdl_malloc(sz))) + goto cleanup; + memset(index.next_ptrs, 0, sz); + + /* lines / 4 + 1 comes from xprepare.c:xdl_prepare_ctx() */ + if (xdl_cha_init(&index.rcha, sizeof(struct record), count1 / 4 + 1) < 0) + goto cleanup; + + index.ptr_shift = line1; + index.max_chain_length = 64; + + memset(&lcs, 0, sizeof(lcs)); + if (find_lcs(&index, &lcs, line1, count1, line2, count2)) + result = fall_back_to_classic_diff(&index, line1, count1, line2, count2); + else { + if (lcs.begin1 == 0 && lcs.begin2 == 0) { + while (count1--) + env->xdf1.rchg[line1++ - 1] = 1; + while (count2--) + env->xdf2.rchg[line2++ - 1] = 1; + result = 0; + } else { + result = histogram_diff(xpp, env, + line1, lcs.begin1 - line1, + line2, lcs.begin2 - line2); + if (result) + goto cleanup; + result = histogram_diff(xpp, env, + lcs.end1 + 1, LINE_END(1) - lcs.end1, + lcs.end2 + 1, LINE_END(2) - lcs.end2); + if (result) + goto cleanup; + } + } + +cleanup: + xdl_free(index.records); + xdl_free(index.line_map); + xdl_free(index.next_ptrs); + xdl_cha_free(&index.rcha); + + return result; +} + +int xdl_do_histogram_diff(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env) +{ + if (xdl_prepare_env(file1, file2, xpp, env) < 0) + return -1; + + return histogram_diff(xpp, env, + env->xdf1.dstart + 1, env->xdf1.dend - env->xdf1.dstart + 1, + env->xdf2.dstart + 1, env->xdf2.dend - env->xdf2.dstart + 1); +} diff --git a/src/xdiff/xinclude.h b/src/xdiff/xinclude.h new file mode 100644 index 000000000..4a1cde909 --- /dev/null +++ b/src/xdiff/xinclude.h @@ -0,0 +1,46 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#if !defined(XINCLUDE_H) +#define XINCLUDE_H + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> + +#ifdef _WIN32 +#else +#include <unistd.h> +#endif + +#include "xmacros.h" +#include "xdiff.h" +#include "xtypes.h" +#include "xutils.h" +#include "xprepare.h" +#include "xdiffi.h" +#include "xemit.h" + + +#endif /* #if !defined(XINCLUDE_H) */ diff --git a/src/xdiff/xmacros.h b/src/xdiff/xmacros.h new file mode 100644 index 000000000..165a895a9 --- /dev/null +++ b/src/xdiff/xmacros.h @@ -0,0 +1,54 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#if !defined(XMACROS_H) +#define XMACROS_H + + + + +#define XDL_MIN(a, b) ((a) < (b) ? (a): (b)) +#define XDL_MAX(a, b) ((a) > (b) ? (a): (b)) +#define XDL_ABS(v) ((v) >= 0 ? (v): -(v)) +#define XDL_ISDIGIT(c) ((c) >= '0' && (c) <= '9') +#define XDL_ISSPACE(c) (isspace((unsigned char)(c))) +#define XDL_ADDBITS(v,b) ((v) + ((v) >> (b))) +#define XDL_MASKBITS(b) ((1UL << (b)) - 1) +#define XDL_HASHLONG(v,b) (XDL_ADDBITS((unsigned long)(v), b) & XDL_MASKBITS(b)) +#define XDL_PTRFREE(p) do { if (p) { xdl_free(p); (p) = NULL; } } while (0) +#define XDL_LE32_PUT(p, v) \ +do { \ + unsigned char *__p = (unsigned char *) (p); \ + *__p++ = (unsigned char) (v); \ + *__p++ = (unsigned char) ((v) >> 8); \ + *__p++ = (unsigned char) ((v) >> 16); \ + *__p = (unsigned char) ((v) >> 24); \ +} while (0) +#define XDL_LE32_GET(p, v) \ +do { \ + unsigned char const *__p = (unsigned char const *) (p); \ + (v) = (unsigned long) __p[0] | ((unsigned long) __p[1]) << 8 | \ + ((unsigned long) __p[2]) << 16 | ((unsigned long) __p[3]) << 24; \ +} while (0) + + +#endif /* #if !defined(XMACROS_H) */ diff --git a/src/xdiff/xmerge.c b/src/xdiff/xmerge.c new file mode 100644 index 000000000..84e424672 --- /dev/null +++ b/src/xdiff/xmerge.c @@ -0,0 +1,619 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003-2006 Davide Libenzi, Johannes E. Schindelin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#include "xinclude.h" + +typedef struct s_xdmerge { + struct s_xdmerge *next; + /* + * 0 = conflict, + * 1 = no conflict, take first, + * 2 = no conflict, take second. + * 3 = no conflict, take both. + */ + int mode; + /* + * These point at the respective postimages. E.g. <i1,chg1> is + * how side #1 wants to change the common ancestor; if there is no + * overlap, lines before i1 in the postimage of side #1 appear + * in the merge result as a region touched by neither side. + */ + long i1, i2; + long chg1, chg2; + /* + * These point at the preimage; of course there is just one + * preimage, that is from the shared common ancestor. + */ + long i0; + long chg0; +} xdmerge_t; + +static int xdl_append_merge(xdmerge_t **merge, int mode, + long i0, long chg0, + long i1, long chg1, + long i2, long chg2) +{ + xdmerge_t *m = *merge; + if (m && (i1 <= m->i1 + m->chg1 || i2 <= m->i2 + m->chg2)) { + if (mode != m->mode) + m->mode = 0; + m->chg0 = i0 + chg0 - m->i0; + m->chg1 = i1 + chg1 - m->i1; + m->chg2 = i2 + chg2 - m->i2; + } else { + m = xdl_malloc(sizeof(xdmerge_t)); + if (!m) + return -1; + m->next = NULL; + m->mode = mode; + m->i0 = i0; + m->chg0 = chg0; + m->i1 = i1; + m->chg1 = chg1; + m->i2 = i2; + m->chg2 = chg2; + if (*merge) + (*merge)->next = m; + *merge = m; + } + return 0; +} + +static int xdl_cleanup_merge(xdmerge_t *c) +{ + int count = 0; + xdmerge_t *next_c; + + /* were there conflicts? */ + for (; c; c = next_c) { + if (c->mode == 0) + count++; + next_c = c->next; + free(c); + } + return count; +} + +static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2, + int line_count, long flags) +{ + int i; + xrecord_t **rec1 = xe1->xdf2.recs + i1; + xrecord_t **rec2 = xe2->xdf2.recs + i2; + + for (i = 0; i < line_count; i++) { + int result = xdl_recmatch(rec1[i]->ptr, rec1[i]->size, + rec2[i]->ptr, rec2[i]->size, flags); + if (!result) + return -1; + } + return 0; +} + +static int xdl_recs_copy_0(int use_orig, xdfenv_t *xe, int i, int count, int add_nl, char *dest) +{ + xrecord_t **recs; + int size = 0; + + recs = (use_orig ? xe->xdf1.recs : xe->xdf2.recs) + i; + + if (count < 1) + return 0; + + for (i = 0; i < count; size += recs[i++]->size) + if (dest) + memcpy(dest + size, recs[i]->ptr, recs[i]->size); + if (add_nl) { + i = recs[count - 1]->size; + if (i == 0 || recs[count - 1]->ptr[i - 1] != '\n') { + if (dest) + dest[size] = '\n'; + size++; + } + } + return size; +} + +static int xdl_recs_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest) +{ + return xdl_recs_copy_0(0, xe, i, count, add_nl, dest); +} + +static int xdl_orig_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest) +{ + return xdl_recs_copy_0(1, xe, i, count, add_nl, dest); +} + +static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, + xdfenv_t *xe2, const char *name2, + const char *name3, + int size, int i, int style, + xdmerge_t *m, char *dest, int marker_size) +{ + 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; + + /* Before conflicting part */ + size += xdl_recs_copy(xe1, i, m->i1 - i, 0, + dest ? dest + size : NULL); + + if (!dest) { + size += marker_size + 1 + marker1_size; + } else { + memset(dest + size, '<', marker_size); + size += marker_size; + if (marker1_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name1, marker1_size - 1); + size += marker1_size; + } + dest[size++] = '\n'; + } + + /* Postimage from side #1 */ + size += xdl_recs_copy(xe1, m->i1, m->chg1, 1, + dest ? dest + size : NULL); + + if (style == XDL_MERGE_DIFF3) { + /* Shared preimage */ + if (!dest) { + size += marker_size + 1 + marker3_size; + } else { + memset(dest + size, '|', marker_size); + size += marker_size; + if (marker3_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name3, marker3_size - 1); + size += marker3_size; + } + dest[size++] = '\n'; + } + size += xdl_orig_copy(xe1, m->i0, m->chg0, 1, + dest ? dest + size : NULL); + } + + if (!dest) { + size += marker_size + 1; + } else { + memset(dest + size, '=', marker_size); + size += marker_size; + dest[size++] = '\n'; + } + + /* Postimage from side #2 */ + size += xdl_recs_copy(xe2, m->i2, m->chg2, 1, + dest ? dest + size : NULL); + if (!dest) { + size += marker_size + 1 + marker2_size; + } else { + memset(dest + size, '>', marker_size); + size += marker_size; + if (marker2_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name2, marker2_size - 1); + size += marker2_size; + } + dest[size++] = '\n'; + } + return size; +} + +static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1, + xdfenv_t *xe2, const char *name2, + const char *ancestor_name, + int favor, + xdmerge_t *m, char *dest, int style, + int marker_size) +{ + int size, i; + + for (size = i = 0; m; m = m->next) { + if (favor && !m->mode) + m->mode = favor; + + if (m->mode == 0) + size = fill_conflict_hunk(xe1, name1, xe2, name2, + ancestor_name, + size, i, style, m, dest, + marker_size); + else if (m->mode & 3) { + /* Before conflicting part */ + size += xdl_recs_copy(xe1, i, m->i1 - i, 0, + dest ? dest + size : NULL); + /* Postimage from side #1 */ + if (m->mode & 1) + size += xdl_recs_copy(xe1, m->i1, m->chg1, 1, + dest ? dest + size : NULL); + /* Postimage from side #2 */ + if (m->mode & 2) + size += xdl_recs_copy(xe2, m->i2, m->chg2, 1, + dest ? dest + size : NULL); + } else + continue; + i = m->i1 + m->chg1; + } + size += xdl_recs_copy(xe1, i, xe1->xdf2.nrec - i, 0, + dest ? dest + size : NULL); + return size; +} + +/* + * Sometimes, changes are not quite identical, but differ in only a few + * lines. Try hard to show only these few lines as conflicting. + */ +static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m, + xpparam_t const *xpp) +{ + for (; m; m = m->next) { + mmfile_t t1, t2; + xdfenv_t xe; + xdchange_t *xscr, *x; + int i1 = m->i1, i2 = m->i2; + + /* let's handle just the conflicts */ + if (m->mode) + continue; + + /* no sense refining a conflict when one side is empty */ + if (m->chg1 == 0 || m->chg2 == 0) + continue; + + /* + * This probably does not work outside git, since + * we have a very simple mmfile structure. + */ + t1.ptr = (char *)xe1->xdf2.recs[m->i1]->ptr; + t1.size = xe1->xdf2.recs[m->i1 + m->chg1 - 1]->ptr + + xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - t1.ptr; + t2.ptr = (char *)xe2->xdf2.recs[m->i2]->ptr; + t2.size = xe2->xdf2.recs[m->i2 + m->chg2 - 1]->ptr + + xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - t2.ptr; + if (xdl_do_diff(&t1, &t2, xpp, &xe) < 0) + return -1; + if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe.xdf2, &xe.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe, &xscr) < 0) { + xdl_free_env(&xe); + return -1; + } + if (!xscr) { + /* If this happens, the changes are identical. */ + xdl_free_env(&xe); + m->mode = 4; + continue; + } + x = xscr; + m->i1 = xscr->i1 + i1; + m->chg1 = xscr->chg1; + m->i2 = xscr->i2 + i2; + m->chg2 = xscr->chg2; + while (xscr->next) { + xdmerge_t *m2 = xdl_malloc(sizeof(xdmerge_t)); + if (!m2) { + xdl_free_env(&xe); + xdl_free_script(x); + return -1; + } + xscr = xscr->next; + m2->next = m->next; + m->next = m2; + m = m2; + m->mode = 0; + m->i1 = xscr->i1 + i1; + m->chg1 = xscr->chg1; + m->i2 = xscr->i2 + i2; + m->chg2 = xscr->chg2; + } + xdl_free_env(&xe); + xdl_free_script(x); + } + return 0; +} + +static int line_contains_alnum(const char *ptr, long size) +{ + while (size--) + if (isalnum((unsigned char)*(ptr++))) + return 1; + return 0; +} + +static int lines_contain_alnum(xdfenv_t *xe, int i, int chg) +{ + for (; chg; chg--, i++) + if (line_contains_alnum(xe->xdf2.recs[i]->ptr, + xe->xdf2.recs[i]->size)) + return 1; + return 0; +} + +/* + * This function merges m and m->next, marking everything between those hunks + * as conflicting, too. + */ +static void xdl_merge_two_conflicts(xdmerge_t *m) +{ + xdmerge_t *next_m = m->next; + m->chg1 = next_m->i1 + next_m->chg1 - m->i1; + m->chg2 = next_m->i2 + next_m->chg2 - m->i2; + m->next = next_m->next; + free(next_m); +} + +/* + * If there are less than 3 non-conflicting lines between conflicts, + * it appears simpler -- because it takes up less (or as many) lines -- + * if the lines are moved into the conflicts. + */ +static int xdl_simplify_non_conflicts(xdfenv_t *xe1, xdmerge_t *m, + int simplify_if_no_alnum) +{ + int result = 0; + + if (!m) + return result; + for (;;) { + xdmerge_t *next_m = m->next; + int begin, end; + + if (!next_m) + return result; + + begin = m->i1 + m->chg1; + end = next_m->i1; + + if (m->mode != 0 || next_m->mode != 0 || + (end - begin > 3 && + (!simplify_if_no_alnum || + lines_contain_alnum(xe1, begin, end - begin)))) { + m = next_m; + } else { + result++; + xdl_merge_two_conflicts(m); + } + } +} + +/* + * level == 0: mark all overlapping changes as conflict + * level == 1: mark overlapping changes as conflict only if not identical + * level == 2: analyze non-identical changes for minimal conflict set + * level == 3: analyze non-identical changes for minimal conflict set, but + * treat hunks not containing any letter or number as conflicting + * + * returns < 0 on error, == 0 for no conflicts, else number of conflicts + */ +static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, + xdfenv_t *xe2, xdchange_t *xscr2, + xmparam_t const *xmp, mmbuffer_t *result) +{ + xdmerge_t *changes, *c; + xpparam_t const *xpp = &xmp->xpp; + const char *const ancestor_name = xmp->ancestor; + const char *const name1 = xmp->file1; + const char *const name2 = xmp->file2; + int i0, i1, i2, chg0, chg1, chg2; + int level = xmp->level; + int style = xmp->style; + int favor = xmp->favor; + + if (style == XDL_MERGE_DIFF3) { + /* + * "diff3 -m" output does not make sense for anything + * more aggressive than XDL_MERGE_EAGER. + */ + if (XDL_MERGE_EAGER < level) + level = XDL_MERGE_EAGER; + } + + c = changes = NULL; + + while (xscr1 && xscr2) { + if (!changes) + changes = c; + if (xscr1->i1 + xscr1->chg1 < xscr2->i1) { + i0 = xscr1->i1; + i1 = xscr1->i2; + i2 = xscr2->i2 - xscr2->i1 + xscr1->i1; + chg0 = xscr1->chg1; + chg1 = xscr1->chg2; + chg2 = xscr1->chg1; + if (xdl_append_merge(&c, 1, + i0, chg0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr1 = xscr1->next; + continue; + } + if (xscr2->i1 + xscr2->chg1 < xscr1->i1) { + i0 = xscr2->i1; + i1 = xscr1->i2 - xscr1->i1 + xscr2->i1; + i2 = xscr2->i2; + chg0 = xscr2->chg1; + chg1 = xscr2->chg1; + chg2 = xscr2->chg2; + if (xdl_append_merge(&c, 2, + i0, chg0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr2 = xscr2->next; + continue; + } + if (level == XDL_MERGE_MINIMAL || xscr1->i1 != xscr2->i1 || + xscr1->chg1 != xscr2->chg1 || + xscr1->chg2 != xscr2->chg2 || + xdl_merge_cmp_lines(xe1, xscr1->i2, + xe2, xscr2->i2, + xscr1->chg2, xpp->flags)) { + /* conflict */ + int off = xscr1->i1 - xscr2->i1; + int ffo = off + xscr1->chg1 - xscr2->chg1; + + i0 = xscr1->i1; + i1 = xscr1->i2; + i2 = xscr2->i2; + if (off > 0) { + i0 -= off; + i1 -= off; + } + else + i2 += off; + chg0 = xscr1->i1 + xscr1->chg1 - i0; + chg1 = xscr1->i2 + xscr1->chg2 - i1; + chg2 = xscr2->i2 + xscr2->chg2 - i2; + if (ffo < 0) { + chg0 -= ffo; + chg1 -= ffo; + } else + chg2 += ffo; + if (xdl_append_merge(&c, 0, + i0, chg0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + } + + i1 = xscr1->i1 + xscr1->chg1; + i2 = xscr2->i1 + xscr2->chg1; + + if (i1 >= i2) + xscr2 = xscr2->next; + if (i2 >= i1) + xscr1 = xscr1->next; + } + while (xscr1) { + if (!changes) + changes = c; + i0 = xscr1->i1; + i1 = xscr1->i2; + i2 = xscr1->i1 + xe2->xdf2.nrec - xe2->xdf1.nrec; + chg0 = xscr1->chg1; + chg1 = xscr1->chg2; + chg2 = xscr1->chg1; + if (xdl_append_merge(&c, 1, + i0, chg0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr1 = xscr1->next; + } + while (xscr2) { + if (!changes) + changes = c; + i0 = xscr2->i1; + i1 = xscr2->i1 + xe1->xdf2.nrec - xe1->xdf1.nrec; + i2 = xscr2->i2; + chg0 = xscr2->chg1; + chg1 = xscr2->chg1; + chg2 = xscr2->chg2; + if (xdl_append_merge(&c, 2, + i0, chg0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr2 = xscr2->next; + } + if (!changes) + changes = c; + /* refine conflicts */ + if (XDL_MERGE_ZEALOUS <= level && + (xdl_refine_conflicts(xe1, xe2, changes, xpp) < 0 || + xdl_simplify_non_conflicts(xe1, changes, + XDL_MERGE_ZEALOUS < level) < 0)) { + xdl_cleanup_merge(changes); + return -1; + } + /* output */ + if (result) { + int marker_size = xmp->marker_size; + int size = xdl_fill_merge_buffer(xe1, name1, xe2, name2, + ancestor_name, + favor, changes, NULL, style, + marker_size); + result->ptr = xdl_malloc(size); + if (!result->ptr) { + xdl_cleanup_merge(changes); + return -1; + } + result->size = size; + xdl_fill_merge_buffer(xe1, name1, xe2, name2, + ancestor_name, favor, changes, + result->ptr, style, marker_size); + } + return xdl_cleanup_merge(changes); +} + +int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2, + xmparam_t const *xmp, mmbuffer_t *result) +{ + xdchange_t *xscr1, *xscr2; + xdfenv_t xe1, xe2; + int status; + xpparam_t const *xpp = &xmp->xpp; + + result->ptr = NULL; + result->size = 0; + + if (xdl_do_diff(orig, mf1, xpp, &xe1) < 0 || + xdl_do_diff(orig, mf2, xpp, &xe2) < 0) { + return -1; + } + if (xdl_change_compact(&xe1.xdf1, &xe1.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe1.xdf2, &xe1.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe1, &xscr1) < 0) { + xdl_free_env(&xe1); + return -1; + } + if (xdl_change_compact(&xe2.xdf1, &xe2.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe2.xdf2, &xe2.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe2, &xscr2) < 0) { + xdl_free_env(&xe2); + return -1; + } + status = 0; + if (!xscr1) { + result->ptr = xdl_malloc(mf2->size); + memcpy(result->ptr, mf2->ptr, mf2->size); + result->size = mf2->size; + } else if (!xscr2) { + result->ptr = xdl_malloc(mf1->size); + memcpy(result->ptr, mf1->ptr, mf1->size); + result->size = mf1->size; + } else { + status = xdl_do_merge(&xe1, xscr1, + &xe2, xscr2, + xmp, result); + } + xdl_free_script(xscr1); + xdl_free_script(xscr2); + + xdl_free_env(&xe1); + xdl_free_env(&xe2); + + return status; +} diff --git a/src/xdiff/xpatience.c b/src/xdiff/xpatience.c new file mode 100644 index 000000000..fdd7d0263 --- /dev/null +++ b/src/xdiff/xpatience.c @@ -0,0 +1,358 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003-2009 Davide Libenzi, Johannes E. Schindelin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ +#include "xinclude.h" +#include "xtypes.h" +#include "xdiff.h" + +/* + * The basic idea of patience diff is to find lines that are unique in + * both files. These are intuitively the ones that we want to see as + * common lines. + * + * The maximal ordered sequence of such line pairs (where ordered means + * that the order in the sequence agrees with the order of the lines in + * both files) naturally defines an initial set of common lines. + * + * Now, the algorithm tries to extend the set of common lines by growing + * the line ranges where the files have identical lines. + * + * Between those common lines, the patience diff algorithm is applied + * recursively, until no unique line pairs can be found; these line ranges + * are handled by the well-known Myers algorithm. + */ + +#define NON_UNIQUE ULONG_MAX + +/* + * This is a hash mapping from line hash to line numbers in the first and + * second file. + */ +struct hashmap { + int nr, alloc; + struct entry { + unsigned long hash; + /* + * 0 = unused entry, 1 = first line, 2 = second, etc. + * line2 is NON_UNIQUE if the line is not unique + * in either the first or the second file. + */ + unsigned long line1, line2; + /* + * "next" & "previous" are used for the longest common + * sequence; + * initially, "next" reflects only the order in file1. + */ + struct entry *next, *previous; + } *entries, *first, *last; + /* were common records found? */ + unsigned long has_matches; + mmfile_t *file1, *file2; + xdfenv_t *env; + xpparam_t const *xpp; +}; + +/* The argument "pass" is 1 for the first file, 2 for the second. */ +static void insert_record(int line, struct hashmap *map, int pass) +{ + xrecord_t **records = pass == 1 ? + map->env->xdf1.recs : map->env->xdf2.recs; + xrecord_t *record = records[line - 1], *other; + /* + * After xdl_prepare_env() (or more precisely, due to + * xdl_classify_record()), the "ha" member of the records (AKA lines) + * is _not_ the hash anymore, but a linearized version of it. In + * other words, the "ha" member is guaranteed to start with 0 and + * the second record's ha can only be 0 or 1, etc. + * + * So we multiply ha by 2 in the hope that the hashing was + * "unique enough". + */ + int index = (int)((record->ha << 1) % map->alloc); + + while (map->entries[index].line1) { + other = map->env->xdf1.recs[map->entries[index].line1 - 1]; + if (map->entries[index].hash != record->ha || + !xdl_recmatch(record->ptr, record->size, + other->ptr, other->size, + map->xpp->flags)) { + if (++index >= map->alloc) + index = 0; + continue; + } + if (pass == 2) + map->has_matches = 1; + if (pass == 1 || map->entries[index].line2) + map->entries[index].line2 = NON_UNIQUE; + else + map->entries[index].line2 = line; + return; + } + if (pass == 2) + return; + map->entries[index].line1 = line; + map->entries[index].hash = record->ha; + if (!map->first) + map->first = map->entries + index; + if (map->last) { + map->last->next = map->entries + index; + map->entries[index].previous = map->last; + } + map->last = map->entries + index; + map->nr++; +} + +/* + * This function has to be called for each recursion into the inter-hunk + * parts, as previously non-unique lines can become unique when being + * restricted to a smaller part of the files. + * + * It is assumed that env has been prepared using xdl_prepare(). + */ +static int fill_hashmap(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env, + struct hashmap *result, + int line1, int count1, int line2, int count2) +{ + result->file1 = file1; + result->file2 = file2; + result->xpp = xpp; + result->env = env; + + /* We know exactly how large we want the hash map */ + result->alloc = count1 * 2; + result->entries = (struct entry *) + xdl_malloc(result->alloc * sizeof(struct entry)); + if (!result->entries) + return -1; + memset(result->entries, 0, result->alloc * sizeof(struct entry)); + + /* First, fill with entries from the first file */ + while (count1--) + insert_record(line1++, result, 1); + + /* Then search for matches in the second file */ + while (count2--) + insert_record(line2++, result, 2); + + return 0; +} + +/* + * Find the longest sequence with a smaller last element (meaning a smaller + * line2, as we construct the sequence with entries ordered by line1). + */ +static int binary_search(struct entry **sequence, int longest, + struct entry *entry) +{ + int left = -1, right = longest; + + while (left + 1 < right) { + int middle = (left + right) / 2; + /* by construction, no two entries can be equal */ + if (sequence[middle]->line2 > entry->line2) + right = middle; + else + left = middle; + } + /* return the index in "sequence", _not_ the sequence length */ + return left; +} + +/* + * The idea is to start with the list of common unique lines sorted by + * the order in file1. For each of these pairs, the longest (partial) + * sequence whose last element's line2 is smaller is determined. + * + * For efficiency, the sequences are kept in a list containing exactly one + * item per sequence length: the sequence with the smallest last + * element (in terms of line2). + */ +static struct entry *find_longest_common_sequence(struct hashmap *map) +{ + struct entry **sequence = xdl_malloc(map->nr * sizeof(struct entry *)); + int longest = 0, i; + struct entry *entry; + + for (entry = map->first; entry; entry = entry->next) { + if (!entry->line2 || entry->line2 == NON_UNIQUE) + continue; + i = binary_search(sequence, longest, entry); + entry->previous = i < 0 ? NULL : sequence[i]; + sequence[++i] = entry; + if (i == longest) + longest++; + } + + /* No common unique lines were found */ + if (!longest) { + xdl_free(sequence); + return NULL; + } + + /* Iterate starting at the last element, adjusting the "next" members */ + entry = sequence[longest - 1]; + entry->next = NULL; + while (entry->previous) { + entry->previous->next = entry; + entry = entry->previous; + } + xdl_free(sequence); + return entry; +} + +static int match(struct hashmap *map, int line1, int line2) +{ + xrecord_t *record1 = map->env->xdf1.recs[line1 - 1]; + xrecord_t *record2 = map->env->xdf2.recs[line2 - 1]; + return xdl_recmatch(record1->ptr, record1->size, + record2->ptr, record2->size, map->xpp->flags); +} + +static int patience_diff(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env, + int line1, int count1, int line2, int count2); + +static int walk_common_sequence(struct hashmap *map, struct entry *first, + int line1, int count1, int line2, int count2) +{ + int end1 = line1 + count1, end2 = line2 + count2; + int next1, next2; + + for (;;) { + /* Try to grow the line ranges of common lines */ + if (first) { + next1 = first->line1; + next2 = first->line2; + while (next1 > line1 && next2 > line2 && + match(map, next1 - 1, next2 - 1)) { + next1--; + next2--; + } + } else { + next1 = end1; + next2 = end2; + } + while (line1 < next1 && line2 < next2 && + match(map, line1, line2)) { + line1++; + line2++; + } + + /* Recurse */ + if (next1 > line1 || next2 > line2) { + struct hashmap submap; + + memset(&submap, 0, sizeof(submap)); + if (patience_diff(map->file1, map->file2, + map->xpp, map->env, + line1, next1 - line1, + line2, next2 - line2)) + return -1; + } + + if (!first) + return 0; + + while (first->next && + first->next->line1 == first->line1 + 1 && + first->next->line2 == first->line2 + 1) + first = first->next; + + line1 = first->line1 + 1; + line2 = first->line2 + 1; + + first = first->next; + } +} + +static int fall_back_to_classic_diff(struct hashmap *map, + int line1, int count1, int line2, int count2) +{ + xpparam_t xpp; + xpp.flags = map->xpp->flags & ~XDF_PATIENCE_DIFF; + + return xdl_fall_back_diff(map->env, &xpp, + line1, count1, line2, count2); +} + +/* + * Recursively find the longest common sequence of unique lines, + * and if none was found, ask xdl_do_diff() to do the job. + * + * This function assumes that env was prepared with xdl_prepare_env(). + */ +static int patience_diff(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env, + int line1, int count1, int line2, int count2) +{ + struct hashmap map; + struct entry *first; + int result = 0; + + /* trivial case: one side is empty */ + if (!count1) { + while(count2--) + env->xdf2.rchg[line2++ - 1] = 1; + return 0; + } else if (!count2) { + while(count1--) + env->xdf1.rchg[line1++ - 1] = 1; + return 0; + } + + memset(&map, 0, sizeof(map)); + if (fill_hashmap(file1, file2, xpp, env, &map, + line1, count1, line2, count2)) + return -1; + + /* are there any matching lines at all? */ + if (!map.has_matches) { + while(count1--) + env->xdf1.rchg[line1++ - 1] = 1; + while(count2--) + env->xdf2.rchg[line2++ - 1] = 1; + xdl_free(map.entries); + return 0; + } + + first = find_longest_common_sequence(&map); + if (first) + result = walk_common_sequence(&map, first, + line1, count1, line2, count2); + else + result = fall_back_to_classic_diff(&map, + line1, count1, line2, count2); + + xdl_free(map.entries); + return result; +} + +int xdl_do_patience_diff(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env) +{ + if (xdl_prepare_env(file1, file2, xpp, env) < 0) + return -1; + + /* environment is cleaned up in xdl_diff() */ + return patience_diff(file1, file2, xpp, env, + 1, env->xdf1.nrec, 1, env->xdf2.nrec); +} diff --git a/src/xdiff/xprepare.c b/src/xdiff/xprepare.c new file mode 100644 index 000000000..e419f4f72 --- /dev/null +++ b/src/xdiff/xprepare.c @@ -0,0 +1,483 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#include "xinclude.h" + + +#define XDL_KPDIS_RUN 4 +#define XDL_MAX_EQLIMIT 1024 +#define XDL_SIMSCAN_WINDOW 100 +#define XDL_GUESS_NLINES1 256 +#define XDL_GUESS_NLINES2 20 + + +typedef struct s_xdlclass { + struct s_xdlclass *next; + unsigned long ha; + char const *line; + long size; + long idx; + long len1, len2; +} xdlclass_t; + +typedef struct s_xdlclassifier { + unsigned int hbits; + long hsize; + xdlclass_t **rchash; + chastore_t ncha; + xdlclass_t **rcrecs; + long alloc; + long count; + long flags; +} xdlclassifier_t; + + + + +static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags); +static void xdl_free_classifier(xdlclassifier_t *cf); +static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash, + unsigned int hbits, xrecord_t *rec); +static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp, + xdlclassifier_t *cf, xdfile_t *xdf); +static void xdl_free_ctx(xdfile_t *xdf); +static int xdl_clean_mmatch(char const *dis, long i, long s, long e); +static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2); +static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2); +static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2); + + + + +static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) { + cf->flags = flags; + + cf->hbits = xdl_hashbits((unsigned int) size); + cf->hsize = 1 << cf->hbits; + + if (xdl_cha_init(&cf->ncha, sizeof(xdlclass_t), size / 4 + 1) < 0) { + + return -1; + } + if (!(cf->rchash = (xdlclass_t **) xdl_malloc(cf->hsize * sizeof(xdlclass_t *)))) { + + xdl_cha_free(&cf->ncha); + return -1; + } + memset(cf->rchash, 0, cf->hsize * sizeof(xdlclass_t *)); + + cf->alloc = size; + if (!(cf->rcrecs = (xdlclass_t **) xdl_malloc(cf->alloc * sizeof(xdlclass_t *)))) { + + xdl_free(cf->rchash); + xdl_cha_free(&cf->ncha); + return -1; + } + + cf->count = 0; + + return 0; +} + + +static void xdl_free_classifier(xdlclassifier_t *cf) { + + xdl_free(cf->rcrecs); + xdl_free(cf->rchash); + xdl_cha_free(&cf->ncha); +} + + +static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash, + unsigned int hbits, xrecord_t *rec) { + long hi; + char const *line; + xdlclass_t *rcrec; + xdlclass_t **rcrecs; + + line = rec->ptr; + hi = (long) XDL_HASHLONG(rec->ha, cf->hbits); + for (rcrec = cf->rchash[hi]; rcrec; rcrec = rcrec->next) + if (rcrec->ha == rec->ha && + xdl_recmatch(rcrec->line, rcrec->size, + rec->ptr, rec->size, cf->flags)) + break; + + if (!rcrec) { + if (!(rcrec = xdl_cha_alloc(&cf->ncha))) { + + return -1; + } + rcrec->idx = cf->count++; + if (cf->count > cf->alloc) { + cf->alloc *= 2; + if (!(rcrecs = (xdlclass_t **) xdl_realloc(cf->rcrecs, cf->alloc * sizeof(xdlclass_t *)))) { + + return -1; + } + cf->rcrecs = rcrecs; + } + cf->rcrecs[rcrec->idx] = rcrec; + rcrec->line = line; + rcrec->size = rec->size; + rcrec->ha = rec->ha; + rcrec->len1 = rcrec->len2 = 0; + rcrec->next = cf->rchash[hi]; + cf->rchash[hi] = rcrec; + } + + (pass == 1) ? rcrec->len1++ : rcrec->len2++; + + rec->ha = (unsigned long) rcrec->idx; + + hi = (long) XDL_HASHLONG(rec->ha, hbits); + rec->next = rhash[hi]; + rhash[hi] = rec; + + return 0; +} + + +static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp, + xdlclassifier_t *cf, xdfile_t *xdf) { + unsigned int hbits; + long nrec, hsize, bsize; + unsigned long hav; + char const *blk, *cur, *top, *prev; + xrecord_t *crec; + xrecord_t **recs, **rrecs; + xrecord_t **rhash; + unsigned long *ha; + char *rchg; + long *rindex; + + ha = NULL; + rindex = NULL; + rchg = NULL; + rhash = NULL; + recs = NULL; + + if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0) + goto abort; + if (!(recs = (xrecord_t **) xdl_malloc(narec * sizeof(xrecord_t *)))) + goto abort; + + if (xpp->flags & XDF_HISTOGRAM_DIFF) + hbits = hsize = 0; + else { + hbits = xdl_hashbits((unsigned int) narec); + hsize = 1 << hbits; + if (!(rhash = (xrecord_t **) xdl_malloc(hsize * sizeof(xrecord_t *)))) + goto abort; + memset(rhash, 0, hsize * sizeof(xrecord_t *)); + } + + nrec = 0; + if ((cur = blk = xdl_mmfile_first(mf, &bsize)) != NULL) { + for (top = blk + bsize; cur < top; ) { + prev = cur; + hav = xdl_hash_record(&cur, top, xpp->flags); + if (nrec >= narec) { + narec *= 2; + if (!(rrecs = (xrecord_t **) xdl_realloc(recs, narec * sizeof(xrecord_t *)))) + goto abort; + recs = rrecs; + } + if (!(crec = xdl_cha_alloc(&xdf->rcha))) + goto abort; + crec->ptr = prev; + crec->size = (long) (cur - prev); + crec->ha = hav; + recs[nrec++] = crec; + + if (!(xpp->flags & XDF_HISTOGRAM_DIFF) && + xdl_classify_record(pass, cf, rhash, hbits, crec) < 0) + goto abort; + } + } + + if (!(rchg = (char *) xdl_malloc((nrec + 2) * sizeof(char)))) + goto abort; + memset(rchg, 0, (nrec + 2) * sizeof(char)); + + if (!(rindex = (long *) xdl_malloc((nrec + 1) * sizeof(long)))) + goto abort; + if (!(ha = (unsigned long *) xdl_malloc((nrec + 1) * sizeof(unsigned long)))) + goto abort; + + xdf->nrec = nrec; + xdf->recs = recs; + xdf->hbits = hbits; + xdf->rhash = rhash; + xdf->rchg = rchg + 1; + xdf->rindex = rindex; + xdf->nreff = 0; + xdf->ha = ha; + xdf->dstart = 0; + xdf->dend = nrec - 1; + + return 0; + +abort: + xdl_free(ha); + xdl_free(rindex); + xdl_free(rchg); + xdl_free(rhash); + xdl_free(recs); + xdl_cha_free(&xdf->rcha); + return -1; +} + + +static void xdl_free_ctx(xdfile_t *xdf) { + + xdl_free(xdf->rhash); + xdl_free(xdf->rindex); + xdl_free(xdf->rchg - 1); + xdl_free(xdf->ha); + xdl_free(xdf->recs); + xdl_cha_free(&xdf->rcha); +} + + +int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *xe) { + long enl1, enl2, sample; + xdlclassifier_t cf; + + memset(&cf, 0, sizeof(cf)); + + /* + * For histogram diff, we can afford a smaller sample size and + * thus a poorer estimate of the number of lines, as the hash + * table (rhash) won't be filled up/grown. The number of lines + * (nrecs) will be updated correctly anyway by + * xdl_prepare_ctx(). + */ + sample = xpp->flags & XDF_HISTOGRAM_DIFF ? XDL_GUESS_NLINES2 : XDL_GUESS_NLINES1; + + enl1 = xdl_guess_lines(mf1, sample) + 1; + enl2 = xdl_guess_lines(mf2, sample) + 1; + + if (!(xpp->flags & XDF_HISTOGRAM_DIFF) && + xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0) { + + return -1; + } + + if (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) { + + xdl_free_classifier(&cf); + return -1; + } + if (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) { + + xdl_free_ctx(&xe->xdf1); + xdl_free_classifier(&cf); + return -1; + } + + if (!(xpp->flags & XDF_PATIENCE_DIFF) && + !(xpp->flags & XDF_HISTOGRAM_DIFF) && + xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) { + + xdl_free_ctx(&xe->xdf2); + xdl_free_ctx(&xe->xdf1); + return -1; + } + + if (!(xpp->flags & XDF_HISTOGRAM_DIFF)) + xdl_free_classifier(&cf); + + return 0; +} + + +void xdl_free_env(xdfenv_t *xe) { + + xdl_free_ctx(&xe->xdf2); + xdl_free_ctx(&xe->xdf1); +} + + +static int xdl_clean_mmatch(char const *dis, long i, long s, long e) { + long r, rdis0, rpdis0, rdis1, rpdis1; + + /* + * Limits the window the is examined during the similar-lines + * scan. The loops below stops when dis[i - r] == 1 (line that + * has no match), but there are corner cases where the loop + * proceed all the way to the extremities by causing huge + * performance penalties in case of big files. + */ + if (i - s > XDL_SIMSCAN_WINDOW) + s = i - XDL_SIMSCAN_WINDOW; + if (e - i > XDL_SIMSCAN_WINDOW) + e = i + XDL_SIMSCAN_WINDOW; + + /* + * Scans the lines before 'i' to find a run of lines that either + * have no match (dis[j] == 0) or have multiple matches (dis[j] > 1). + * Note that we always call this function with dis[i] > 1, so the + * current line (i) is already a multimatch line. + */ + for (r = 1, rdis0 = 0, rpdis0 = 1; (i - r) >= s; r++) { + if (!dis[i - r]) + rdis0++; + else if (dis[i - r] == 2) + rpdis0++; + else + break; + } + /* + * If the run before the line 'i' found only multimatch lines, we + * return 0 and hence we don't make the current line (i) discarded. + * We want to discard multimatch lines only when they appear in the + * middle of runs with nomatch lines (dis[j] == 0). + */ + if (rdis0 == 0) + return 0; + for (r = 1, rdis1 = 0, rpdis1 = 1; (i + r) <= e; r++) { + if (!dis[i + r]) + rdis1++; + else if (dis[i + r] == 2) + rpdis1++; + else + break; + } + /* + * If the run after the line 'i' found only multimatch lines, we + * return 0 and hence we don't make the current line (i) discarded. + */ + if (rdis1 == 0) + return 0; + rdis1 += rdis0; + rpdis1 += rpdis0; + + return rpdis1 * XDL_KPDIS_RUN < (rpdis1 + rdis1); +} + + +/* + * Try to reduce the problem complexity, discard records that have no + * matches on the other file. Also, lines that have multiple matches + * might be potentially discarded if they happear in a run of discardable. + */ +static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) { + long i, nm, nreff, mlim; + xrecord_t **recs; + xdlclass_t *rcrec; + char *dis, *dis1, *dis2; + + if (!(dis = (char *) xdl_malloc(xdf1->nrec + xdf2->nrec + 2))) { + + return -1; + } + memset(dis, 0, xdf1->nrec + xdf2->nrec + 2); + dis1 = dis; + dis2 = dis1 + xdf1->nrec + 1; + + if ((mlim = xdl_bogosqrt(xdf1->nrec)) > XDL_MAX_EQLIMIT) + mlim = XDL_MAX_EQLIMIT; + for (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; i <= xdf1->dend; i++, recs++) { + rcrec = cf->rcrecs[(*recs)->ha]; + nm = rcrec ? rcrec->len2 : 0; + dis1[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1; + } + + if ((mlim = xdl_bogosqrt(xdf2->nrec)) > XDL_MAX_EQLIMIT) + mlim = XDL_MAX_EQLIMIT; + for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) { + rcrec = cf->rcrecs[(*recs)->ha]; + nm = rcrec ? rcrec->len1 : 0; + dis2[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1; + } + + for (nreff = 0, i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; + i <= xdf1->dend; i++, recs++) { + if (dis1[i] == 1 || + (dis1[i] == 2 && !xdl_clean_mmatch(dis1, i, xdf1->dstart, xdf1->dend))) { + xdf1->rindex[nreff] = i; + xdf1->ha[nreff] = (*recs)->ha; + nreff++; + } else + xdf1->rchg[i] = 1; + } + xdf1->nreff = nreff; + + for (nreff = 0, i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; + i <= xdf2->dend; i++, recs++) { + if (dis2[i] == 1 || + (dis2[i] == 2 && !xdl_clean_mmatch(dis2, i, xdf2->dstart, xdf2->dend))) { + xdf2->rindex[nreff] = i; + xdf2->ha[nreff] = (*recs)->ha; + nreff++; + } else + xdf2->rchg[i] = 1; + } + xdf2->nreff = nreff; + + xdl_free(dis); + + return 0; +} + + +/* + * Early trim initial and terminal matching records. + */ +static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) { + long i, lim; + xrecord_t **recs1, **recs2; + + recs1 = xdf1->recs; + recs2 = xdf2->recs; + for (i = 0, lim = XDL_MIN(xdf1->nrec, xdf2->nrec); i < lim; + i++, recs1++, recs2++) + if ((*recs1)->ha != (*recs2)->ha) + break; + + xdf1->dstart = xdf2->dstart = i; + + recs1 = xdf1->recs + xdf1->nrec - 1; + recs2 = xdf2->recs + xdf2->nrec - 1; + for (lim -= i, i = 0; i < lim; i++, recs1--, recs2--) + if ((*recs1)->ha != (*recs2)->ha) + break; + + xdf1->dend = xdf1->nrec - i - 1; + xdf2->dend = xdf2->nrec - i - 1; + + return 0; +} + + +static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) { + + if (xdl_trim_ends(xdf1, xdf2) < 0 || + xdl_cleanup_records(cf, xdf1, xdf2) < 0) { + + return -1; + } + + return 0; +} diff --git a/src/xdiff/xprepare.h b/src/xdiff/xprepare.h new file mode 100644 index 000000000..8fb06a537 --- /dev/null +++ b/src/xdiff/xprepare.h @@ -0,0 +1,34 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#if !defined(XPREPARE_H) +#define XPREPARE_H + + + +int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *xe); +void xdl_free_env(xdfenv_t *xe); + + + +#endif /* #if !defined(XPREPARE_H) */ diff --git a/src/xdiff/xtypes.h b/src/xdiff/xtypes.h new file mode 100644 index 000000000..2511aef8d --- /dev/null +++ b/src/xdiff/xtypes.h @@ -0,0 +1,67 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#if !defined(XTYPES_H) +#define XTYPES_H + + + +typedef struct s_chanode { + struct s_chanode *next; + long icurr; +} chanode_t; + +typedef struct s_chastore { + chanode_t *head, *tail; + long isize, nsize; + chanode_t *ancur; + chanode_t *sncur; + long scurr; +} chastore_t; + +typedef struct s_xrecord { + struct s_xrecord *next; + char const *ptr; + long size; + unsigned long ha; +} xrecord_t; + +typedef struct s_xdfile { + chastore_t rcha; + long nrec; + unsigned int hbits; + xrecord_t **rhash; + long dstart, dend; + xrecord_t **recs; + char *rchg; + long *rindex; + long nreff; + unsigned long *ha; +} xdfile_t; + +typedef struct s_xdfenv { + xdfile_t xdf1, xdf2; +} xdfenv_t; + + + +#endif /* #if !defined(XTYPES_H) */ diff --git a/src/xdiff/xutils.c b/src/xdiff/xutils.c new file mode 100644 index 000000000..bb7bdee49 --- /dev/null +++ b/src/xdiff/xutils.c @@ -0,0 +1,419 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#include "xinclude.h" + + + + +long xdl_bogosqrt(long n) { + long i; + + /* + * Classical integer square root approximation using shifts. + */ + for (i = 1; n > 0; n >>= 2) + i <<= 1; + + return i; +} + + +int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize, + xdemitcb_t *ecb) { + int i = 2; + mmbuffer_t mb[3]; + + mb[0].ptr = (char *) pre; + mb[0].size = psize; + mb[1].ptr = (char *) rec; + mb[1].size = size; + if (size > 0 && rec[size - 1] != '\n') { + mb[2].ptr = (char *) "\n\\ No newline at end of file\n"; + mb[2].size = strlen(mb[2].ptr); + i++; + } + if (ecb->outf(ecb->priv, mb, i) < 0) { + + return -1; + } + + return 0; +} + +void *xdl_mmfile_first(mmfile_t *mmf, long *size) +{ + *size = (long)mmf->size; + return mmf->ptr; +} + + +long xdl_mmfile_size(mmfile_t *mmf) +{ + return (long)mmf->size; +} + + +int xdl_cha_init(chastore_t *cha, long isize, long icount) { + + cha->head = cha->tail = NULL; + cha->isize = isize; + cha->nsize = icount * isize; + cha->ancur = cha->sncur = NULL; + cha->scurr = 0; + + return 0; +} + + +void xdl_cha_free(chastore_t *cha) { + chanode_t *cur, *tmp; + + for (cur = cha->head; (tmp = cur) != NULL;) { + cur = cur->next; + xdl_free(tmp); + } +} + + +void *xdl_cha_alloc(chastore_t *cha) { + chanode_t *ancur; + void *data; + + if (!(ancur = cha->ancur) || ancur->icurr == cha->nsize) { + if (!(ancur = (chanode_t *) xdl_malloc(sizeof(chanode_t) + cha->nsize))) { + + return NULL; + } + ancur->icurr = 0; + ancur->next = NULL; + if (cha->tail) + cha->tail->next = ancur; + if (!cha->head) + cha->head = ancur; + cha->tail = ancur; + cha->ancur = ancur; + } + + data = (char *) ancur + sizeof(chanode_t) + ancur->icurr; + ancur->icurr += cha->isize; + + return data; +} + + +void *xdl_cha_first(chastore_t *cha) { + chanode_t *sncur; + + if (!(cha->sncur = sncur = cha->head)) + return NULL; + + cha->scurr = 0; + + return (char *) sncur + sizeof(chanode_t) + cha->scurr; +} + + +void *xdl_cha_next(chastore_t *cha) { + chanode_t *sncur; + + if (!(sncur = cha->sncur)) + return NULL; + cha->scurr += cha->isize; + if (cha->scurr == sncur->icurr) { + if (!(sncur = cha->sncur = sncur->next)) + return NULL; + cha->scurr = 0; + } + + return (char *) sncur + sizeof(chanode_t) + cha->scurr; +} + + +long xdl_guess_lines(mmfile_t *mf, long sample) { + long nl = 0, size, tsize = 0; + char const *data, *cur, *top; + + if ((cur = data = xdl_mmfile_first(mf, &size)) != NULL) { + for (top = data + size; nl < sample && cur < top; ) { + nl++; + if (!(cur = memchr(cur, '\n', top - cur))) + cur = top; + else + cur++; + } + tsize += (long) (cur - data); + } + + if (nl && tsize) + nl = xdl_mmfile_size(mf) / (tsize / nl); + + return nl + 1; +} + +int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags) +{ + int i1, i2; + + if (s1 == s2 && !memcmp(l1, l2, s1)) + return 1; + if (!(flags & XDF_WHITESPACE_FLAGS)) + return 0; + + i1 = 0; + i2 = 0; + + /* + * -w matches everything that matches with -b, and -b in turn + * matches everything that matches with --ignore-space-at-eol. + * + * Each flavor of ignoring needs different logic to skip whitespaces + * while we have both sides to compare. + */ + if (flags & XDF_IGNORE_WHITESPACE) { + goto skip_ws; + while (i1 < s1 && i2 < s2) { + if (l1[i1++] != l2[i2++]) + return 0; + skip_ws: + while (i1 < s1 && XDL_ISSPACE(l1[i1])) + i1++; + while (i2 < s2 && XDL_ISSPACE(l2[i2])) + i2++; + } + } else if (flags & XDF_IGNORE_WHITESPACE_CHANGE) { + while (i1 < s1 && i2 < s2) { + if (XDL_ISSPACE(l1[i1]) && XDL_ISSPACE(l2[i2])) { + /* Skip matching spaces and try again */ + while (i1 < s1 && XDL_ISSPACE(l1[i1])) + i1++; + while (i2 < s2 && XDL_ISSPACE(l2[i2])) + i2++; + continue; + } + if (l1[i1++] != l2[i2++]) + return 0; + } + } else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL) { + while (i1 < s1 && i2 < s2 && l1[i1++] == l2[i2++]) + ; /* keep going */ + } + + /* + * After running out of one side, the remaining side must have + * nothing but whitespace for the lines to match. Note that + * ignore-whitespace-at-eol case may break out of the loop + * while there still are characters remaining on both lines. + */ + if (i1 < s1) { + while (i1 < s1 && XDL_ISSPACE(l1[i1])) + i1++; + if (s1 != i1) + return 0; + } + if (i2 < s2) { + while (i2 < s2 && XDL_ISSPACE(l2[i2])) + i2++; + return (s2 == i2); + } + return 1; +} + +static unsigned long xdl_hash_record_with_whitespace(char const **data, + char const *top, long flags) { + unsigned long ha = 5381; + char const *ptr = *data; + + for (; ptr < top && *ptr != '\n'; ptr++) { + if (XDL_ISSPACE(*ptr)) { + const char *ptr2 = ptr; + int at_eol; + while (ptr + 1 < top && XDL_ISSPACE(ptr[1]) + && ptr[1] != '\n') + ptr++; + at_eol = (top <= ptr + 1 || ptr[1] == '\n'); + if (flags & XDF_IGNORE_WHITESPACE) + ; /* already handled */ + else if (flags & XDF_IGNORE_WHITESPACE_CHANGE + && !at_eol) { + ha += (ha << 5); + ha ^= (unsigned long) ' '; + } + else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL + && !at_eol) { + while (ptr2 != ptr + 1) { + ha += (ha << 5); + ha ^= (unsigned long) *ptr2; + ptr2++; + } + } + continue; + } + ha += (ha << 5); + ha ^= (unsigned long) *ptr; + } + *data = ptr < top ? ptr + 1: ptr; + + return ha; +} + + +unsigned long xdl_hash_record(char const **data, char const *top, long flags) { + unsigned long ha = 5381; + char const *ptr = *data; + + if (flags & XDF_WHITESPACE_FLAGS) + return xdl_hash_record_with_whitespace(data, top, flags); + + for (; ptr < top && *ptr != '\n'; ptr++) { + ha += (ha << 5); + ha ^= (unsigned long) *ptr; + } + *data = ptr < top ? ptr + 1: ptr; + + return ha; +} + + +unsigned int xdl_hashbits(unsigned int size) { + unsigned int val = 1, bits = 0; + + for (; val < size && bits < CHAR_BIT * sizeof(unsigned int); val <<= 1, bits++); + return bits ? bits: 1; +} + + +int xdl_num_out(char *out, long val) { + char *ptr, *str = out; + char buf[32]; + + ptr = buf + sizeof(buf) - 1; + *ptr = '\0'; + if (val < 0) { + *--ptr = '-'; + val = -val; + } + for (; val && ptr > buf; val /= 10) + *--ptr = "0123456789"[val % 10]; + if (*ptr) + for (; *ptr; ptr++, str++) + *str = *ptr; + else + *str++ = '0'; + *str = '\0'; + + return (int)(str - out); +} + + +long xdl_atol(char const *str, char const **next) { + long val, base; + char const *top; + + for (top = str; XDL_ISDIGIT(*top); top++); + if (next) + *next = top; + for (val = 0, base = 1, top--; top >= str; top--, base *= 10) + val += base * (long)(*top - '0'); + return val; +} + + +int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2, + const char *func, long funclen, xdemitcb_t *ecb) { + int nb = 0; + mmbuffer_t mb; + char buf[128]; + + memcpy(buf, "@@ -", 4); + nb += 4; + + nb += xdl_num_out(buf + nb, c1 ? s1: s1 - 1); + + if (c1 != 1) { + memcpy(buf + nb, ",", 1); + nb += 1; + + nb += xdl_num_out(buf + nb, c1); + } + + memcpy(buf + nb, " +", 2); + nb += 2; + + nb += xdl_num_out(buf + nb, c2 ? s2: s2 - 1); + + if (c2 != 1) { + memcpy(buf + nb, ",", 1); + nb += 1; + + nb += xdl_num_out(buf + nb, c2); + } + + memcpy(buf + nb, " @@", 3); + nb += 3; + if (func && funclen) { + buf[nb++] = ' '; + if (funclen > (long)sizeof(buf) - nb - 1) + funclen = (long)sizeof(buf) - nb - 1; + memcpy(buf + nb, func, funclen); + nb += funclen; + } + buf[nb++] = '\n'; + + mb.ptr = buf; + mb.size = nb; + if (ecb->outf(ecb->priv, &mb, 1) < 0) + return -1; + + return 0; +} + +int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp, + int line1, int count1, int line2, int count2) +{ + /* + * This probably does not work outside Git, since + * we have a very simple mmfile structure. + * + * Note: ideally, we would reuse the prepared environment, but + * the libxdiff interface does not (yet) allow for diffing only + * ranges of lines instead of the whole files. + */ + mmfile_t subfile1, subfile2; + xdfenv_t env; + + subfile1.ptr = (char *)diff_env->xdf1.recs[line1 - 1]->ptr; + subfile1.size = diff_env->xdf1.recs[line1 + count1 - 2]->ptr + + diff_env->xdf1.recs[line1 + count1 - 2]->size - subfile1.ptr; + subfile2.ptr = (char *)diff_env->xdf2.recs[line2 - 1]->ptr; + subfile2.size = diff_env->xdf2.recs[line2 + count2 - 2]->ptr + + diff_env->xdf2.recs[line2 + count2 - 2]->size - subfile2.ptr; + if (xdl_do_diff(&subfile1, &subfile2, xpp, &env) < 0) + return -1; + + memcpy(diff_env->xdf1.rchg + line1 - 1, env.xdf1.rchg, count1); + memcpy(diff_env->xdf2.rchg + line2 - 1, env.xdf2.rchg, count2); + + xdl_free_env(&env); + + return 0; +} diff --git a/src/xdiff/xutils.h b/src/xdiff/xutils.h new file mode 100644 index 000000000..714719a89 --- /dev/null +++ b/src/xdiff/xutils.h @@ -0,0 +1,49 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003 Davide Libenzi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#if !defined(XUTILS_H) +#define XUTILS_H + + + +long xdl_bogosqrt(long n); +int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize, + xdemitcb_t *ecb); +int xdl_cha_init(chastore_t *cha, long isize, long icount); +void xdl_cha_free(chastore_t *cha); +void *xdl_cha_alloc(chastore_t *cha); +void *xdl_cha_first(chastore_t *cha); +void *xdl_cha_next(chastore_t *cha); +long xdl_guess_lines(mmfile_t *mf, long sample); +int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags); +unsigned long xdl_hash_record(char const **data, char const *top, long flags); +unsigned int xdl_hashbits(unsigned int size); +int xdl_num_out(char *out, long val); +long xdl_atol(char const *str, char const **next); +int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2, + const char *func, long funclen, xdemitcb_t *ecb); +int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp, + int line1, int count1, int line2, int count2); + + + +#endif /* #if !defined(XUTILS_H) */ |