diff options
Diffstat (limited to 'src')
82 files changed, 4332 insertions, 2801 deletions
diff --git a/src/attr.c b/src/attr.c index 9c88771e3..979fecc14 100644 --- a/src/attr.c +++ b/src/attr.c @@ -1,6 +1,8 @@ #include "repository.h" #include "fileops.h" #include "config.h" +#include "attr.h" +#include "ignore.h" #include "git2/oid.h" #include <ctype.h> @@ -593,17 +595,28 @@ static int collect_attr_files( return error; } -static char *try_global_default(const char *relpath) +static int attr_cache__lookup_path( + const char **out, git_config *cfg, const char *key, const char *fallback) { - git_buf dflt = GIT_BUF_INIT; - char *rval = NULL; + git_buf buf = GIT_BUF_INIT; + int error; - if (!git_futils_find_global_file(&dflt, relpath)) - rval = git_buf_detach(&dflt); + if (!(error = git_config_get_string(out, cfg, key))) + return 0; - git_buf_free(&dflt); + if (error == GIT_ENOTFOUND) { + giterr_clear(); + error = 0; - return rval; + if (!git_futils_find_xdg_file(&buf, fallback)) + *out = git_buf_detach(&buf); + else + *out = NULL; + + git_buf_free(&buf); + } + + return error; } int git_attr_cache__init(git_repository *repo) @@ -619,19 +632,15 @@ int git_attr_cache__init(git_repository *repo) 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) + ret = attr_cache__lookup_path( + &cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG, GIT_ATTR_FILE_XDG); + if (ret < 0) return ret; - if (ret == GIT_ENOTFOUND) - cache->cfg_attr_file = try_global_default(GIT_ATTR_CONFIG_DEFAULT); - ret = git_config_get_string(&cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG); - if (ret < 0 && ret != GIT_ENOTFOUND) + ret = attr_cache__lookup_path( + &cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG, GIT_IGNORE_FILE_XDG); + if (ret < 0) return ret; - if (ret == GIT_ENOTFOUND) - cache->cfg_excl_file = try_global_default(GIT_IGNORE_CONFIG_DEFAULT); - - giterr_clear(); /* allocate hashtable for attribute and ignore file contents */ if (cache->files == NULL) { diff --git a/src/attr.h b/src/attr.h index 0fc33089b..19c979bcd 100644 --- a/src/attr.h +++ b/src/attr.h @@ -8,27 +8,13 @@ #define INCLUDE_attr_h__ #include "attr_file.h" -#include "strmap.h" - -#define GIT_ATTR_CONFIG "core.attributesfile" -#define GIT_ATTR_CONFIG_DEFAULT ".config/git/attributes" -#define GIT_IGNORE_CONFIG "core.excludesfile" -#define GIT_IGNORE_CONFIG_DEFAULT ".config/git/ignore" - -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; + +#define GIT_ATTR_CONFIG "core.attributesfile" +#define GIT_IGNORE_CONFIG "core.excludesfile" typedef int (*git_attr_file_parser)( git_repository *, void *, const char *, git_attr_file *); -extern int git_attr_cache__init(git_repository *repo); - extern int git_attr_cache__insert_macro( git_repository *repo, git_attr_rule *macro); diff --git a/src/attr_file.c b/src/attr_file.c index 628cb1544..74bd2133f 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -1,6 +1,7 @@ #include "common.h" #include "repository.h" #include "filebuf.h" +#include "attr.h" #include "git2/blob.h" #include "git2/tree.h" #include <ctype.h> diff --git a/src/attr_file.h b/src/attr_file.h index 8dc8303f7..2cc8546a2 100644 --- a/src/attr_file.h +++ b/src/attr_file.h @@ -17,6 +17,7 @@ #define GIT_ATTR_FILE ".gitattributes" #define GIT_ATTR_FILE_INREPO "info/attributes" #define GIT_ATTR_FILE_SYSTEM "gitattributes" +#define GIT_ATTR_FILE_XDG "attributes" #define GIT_ATTR_FNMATCH_NEGATIVE (1U << 0) #define GIT_ATTR_FNMATCH_DIRECTORY (1U << 1) diff --git a/src/attrcache.h b/src/attrcache.h new file mode 100644 index 000000000..12cec4bfb --- /dev/null +++ b/src/attrcache.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * 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_attrcache_h__ +#define INCLUDE_attrcache_h__ + +#include "pool.h" +#include "strmap.h" + +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; + +extern int git_attr_cache__init(git_repository *repo); + +#endif diff --git a/src/blob.c b/src/blob.c index bcb6ac96b..c0514fc13 100644 --- a/src/blob.c +++ b/src/blob.c @@ -12,6 +12,7 @@ #include "common.h" #include "blob.h" #include "filter.h" +#include "buf_text.h" const void *git_blob_rawcontent(const git_blob *blob) { @@ -221,7 +222,9 @@ int git_blob_create_fromworkdir(git_oid *oid, git_repository *repo, const char * return -1; } - error = blob_create_internal(oid, repo, git_buf_cstr(&full_path), git_buf_cstr(&full_path), true); + error = blob_create_internal( + oid, repo, git_buf_cstr(&full_path), + git_buf_cstr(&full_path) + strlen(workdir), true); git_buf_free(&full_path); return error; @@ -231,13 +234,21 @@ int git_blob_create_fromdisk(git_oid *oid, git_repository *repo, const char *pat { int error; git_buf full_path = GIT_BUF_INIT; + const char *workdir, *hintpath; if ((error = git_path_prettify(&full_path, path, NULL)) < 0) { git_buf_free(&full_path); return error; } - error = blob_create_internal(oid, repo, git_buf_cstr(&full_path), git_buf_cstr(&full_path), true); + hintpath = git_buf_cstr(&full_path); + workdir = git_repository_workdir(repo); + + if (workdir && !git__prefixcmp(hintpath, workdir)) + hintpath += strlen(workdir); + + error = blob_create_internal( + oid, repo, git_buf_cstr(&full_path), hintpath, true); git_buf_free(&full_path); return error; diff --git a/src/branch.c b/src/branch.c index a50387541..45ecca751 100644 --- a/src/branch.c +++ b/src/branch.c @@ -54,11 +54,11 @@ static int not_a_local_branch(const char *reference_name) } int git_branch_create( - git_reference **ref_out, - git_repository *repository, - const char *branch_name, - const git_commit *commit, - int force) + git_reference **ref_out, + git_repository *repository, + const char *branch_name, + const git_commit *commit, + int force) { git_reference *branch = NULL; git_buf canonical_branch_name = GIT_BUF_INIT; @@ -124,10 +124,7 @@ on_error: } typedef struct { - int (*branch_cb)( - const char *branch_name, - git_branch_t branch_type, - void *payload); + git_branch_foreach_cb branch_cb; void *callback_payload; unsigned int branch_type; } branch_foreach_filter; @@ -148,14 +145,10 @@ static int branch_foreach_cb(const char *branch_name, void *payload) } int git_branch_foreach( - git_repository *repo, - unsigned int list_flags, - int (*branch_cb)( - const char *branch_name, - git_branch_t branch_type, - void *payload), - void *payload -) + git_repository *repo, + unsigned int list_flags, + git_branch_foreach_cb branch_cb, + void *payload) { branch_foreach_filter filter; @@ -167,6 +160,7 @@ int git_branch_foreach( } int git_branch_move( + git_reference **out, git_reference *branch, const char *new_branch_name, int force) @@ -181,28 +175,20 @@ int git_branch_move( if (!git_reference_is_branch(branch)) return not_a_local_branch(git_reference_name(branch)); - if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0) - goto cleanup; - - if (git_buf_printf( - &old_config_section, - "branch.%s", - git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0) - goto cleanup; - - if ((error = git_reference_rename(branch, git_buf_cstr(&new_reference_name), force)) < 0) - goto cleanup; + if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0 || + (error = git_buf_printf(&old_config_section, "branch.%s", git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR))) < 0 || + (error = git_buf_printf(&new_config_section, "branch.%s", new_branch_name)) < 0) + goto done; - if (git_buf_printf(&new_config_section, "branch.%s", new_branch_name) < 0) - goto cleanup; - - if ((error = git_config_rename_section( - git_reference_owner(branch), + if ((error = git_config_rename_section(git_reference_owner(branch), git_buf_cstr(&old_config_section), git_buf_cstr(&new_config_section))) < 0) - goto cleanup; + goto done; + + if ((error = git_reference_rename(out, branch, git_buf_cstr(&new_reference_name), force)) < 0) + goto done; -cleanup: +done: git_buf_free(&new_reference_name); git_buf_free(&old_config_section); git_buf_free(&new_config_section); @@ -211,10 +197,10 @@ cleanup: } int git_branch_lookup( - git_reference **ref_out, - git_repository *repo, - const char *branch_name, - git_branch_t branch_type) + git_reference **ref_out, + git_repository *repo, + const char *branch_name, + git_branch_t branch_type) { assert(ref_out && repo && branch_name); diff --git a/src/buf_text.c b/src/buf_text.c index 3a8f442b4..443454b5f 100644 --- a/src/buf_text.c +++ b/src/buf_text.c @@ -60,6 +60,83 @@ void git_buf_text_unescape(git_buf *buf) buf->size = git__unescape(buf->ptr); } +int git_buf_text_crlf_to_lf(git_buf *tgt, const git_buf *src) +{ + const char *scan = src->ptr; + const char *scan_end = src->ptr + src->size; + const char *next = memchr(scan, '\r', src->size); + char *out; + + assert(tgt != src); + + if (!next) + return GIT_ENOTFOUND; + + /* reduce reallocs while in the loop */ + if (git_buf_grow(tgt, src->size) < 0) + return -1; + out = tgt->ptr; + tgt->size = 0; + + /* Find the next \r and copy whole chunk up to there to tgt */ + for (; next; scan = next + 1, next = memchr(scan, '\r', scan_end - scan)) { + if (next > scan) { + size_t copylen = next - scan; + memcpy(out, scan, copylen); + out += copylen; + } + + /* Do not drop \r unless it is followed by \n */ + if (next[1] != '\n') + *out++ = '\r'; + } + + /* Copy remaining input into dest */ + memcpy(out, scan, scan_end - scan + 1); /* +1 for NUL byte */ + out += (scan_end - scan); + tgt->size = out - tgt->ptr; + + return 0; +} + +int git_buf_text_lf_to_crlf(git_buf *tgt, const git_buf *src) +{ + const char *start = src->ptr; + const char *end = start + src->size; + const char *scan = start; + const char *next = memchr(scan, '\n', src->size); + + assert(tgt != src); + + if (!next) + return GIT_ENOTFOUND; + + /* attempt to reduce reallocs while in the loop */ + if (git_buf_grow(tgt, src->size + (src->size >> 4) + 1) < 0) + return -1; + tgt->size = 0; + + for (; next; scan = next + 1, next = memchr(scan, '\n', end - scan)) { + size_t copylen = next - scan; + /* don't convert existing \r\n to \r\r\n */ + size_t extralen = (next > start && next[-1] == '\r') ? 1 : 2; + size_t needsize = tgt->size + copylen + extralen + 1; + + if (tgt->asize < needsize && git_buf_grow(tgt, needsize) < 0) + return -1; + + if (next > scan) { + memcpy(tgt->ptr + tgt->size, scan, copylen); + tgt->size += copylen; + } + if (extralen == 2) + tgt->ptr[tgt->size++] = '\r'; + tgt->ptr[tgt->size++] = '\n'; + } + + return git_buf_put(tgt, scan, end - scan); +} + int git_buf_text_common_prefix(git_buf *buf, const git_strarray *strings) { size_t i; diff --git a/src/buf_text.h b/src/buf_text.h index 458ee33c9..58e4e26a7 100644 --- a/src/buf_text.h +++ b/src/buf_text.h @@ -56,6 +56,20 @@ GIT_INLINE(int) git_buf_text_puts_escape_regex(git_buf *buf, const char *string) extern void git_buf_text_unescape(git_buf *buf); /** + * Replace all \r\n with \n (or do nothing if no \r\n are found) + * + * @return 0 on success, GIT_ENOTFOUND if no \r\n, -1 on memory error + */ +extern int git_buf_text_crlf_to_lf(git_buf *tgt, const git_buf *src); + +/** + * Replace all \n with \r\n (or do nothing if no \n are found) + * + * @return 0 on success, GIT_ENOTFOUND if no \n, -1 on memory error + */ +extern int git_buf_text_lf_to_crlf(git_buf *tgt, const git_buf *src); + +/** * Fill buffer with the common prefix of a array of strings * * Buffer will be set to empty if there is no common prefix diff --git a/src/buffer.h b/src/buffer.h index 6e73895b4..5402f3827 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -119,7 +119,7 @@ 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) +GIT_INLINE(ssize_t) git_buf_rfind_next(const git_buf *buf, char ch) { ssize_t idx = (ssize_t)buf->size - 1; while (idx >= 0 && buf->ptr[idx] == ch) idx--; @@ -127,18 +127,17 @@ GIT_INLINE(ssize_t) git_buf_rfind_next(git_buf *buf, char ch) return idx; } -GIT_INLINE(ssize_t) git_buf_rfind(git_buf *buf, char ch) +GIT_INLINE(ssize_t) git_buf_rfind(const git_buf *buf, char ch) { ssize_t idx = (ssize_t)buf->size - 1; while (idx >= 0 && buf->ptr[idx] != ch) idx--; return idx; } -GIT_INLINE(ssize_t) git_buf_find(git_buf *buf, char ch) +GIT_INLINE(ssize_t) git_buf_find(const git_buf *buf, char ch) { - size_t idx = 0; - while (idx < buf->size && buf->ptr[idx] != ch) idx++; - return (idx == buf->size) ? -1 : (ssize_t)idx; + void *found = memchr(buf->ptr, ch, buf->size); + return found ? (ssize_t)((const char *)found - buf->ptr) : -1; } /* Remove whitespace from the end of the buffer */ diff --git a/src/checkout.c b/src/checkout.c index 19ac913d3..24fa21024 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -23,6 +23,7 @@ #include "blob.h" #include "diff.h" #include "pathspec.h" +#include "buf_text.h" /* See docs/checkout-internals.md for more information */ @@ -234,10 +235,13 @@ static int checkout_action_wd_only( /* check if item is tracked in the index but not in the checkout diff */ if (data->index != NULL) { if (wd->mode != GIT_FILEMODE_TREE) { - if (git_index_get_bypath(data->index, wd->path, 0) != NULL) { + int error; + + if ((error = git_index_find(NULL, data->index, wd->path)) == 0) { notify = GIT_CHECKOUT_NOTIFY_DIRTY; remove = ((data->strategy & GIT_CHECKOUT_FORCE) != 0); - } + } else if (error != GIT_ENOTFOUND) + return error; } else { /* for tree entries, we have to see if there are any index * entries that are contained inside that tree @@ -456,7 +460,7 @@ static int checkout_action( while (1) { if (!wd) return checkout_action_no_wd(data, delta); - + cmp = strcomp(wd->path, delta->old_file.path); /* 1. wd before delta ("a/a" before "a/b") @@ -473,9 +477,9 @@ static int checkout_action( if (cmp == 0) { if (wd->mode == GIT_FILEMODE_TREE) { /* case 2 - entry prefixed by workdir tree */ - if (git_iterator_advance_into_directory(workdir, &wd) < 0) + if (git_iterator_advance_into(&wd, workdir) < 0) goto fail; - + *wditem_ptr = wd; continue; } @@ -484,14 +488,14 @@ static int checkout_action( if (delta->old_file.path[strlen(wd->path)] == '/') { act = checkout_action_with_wd_blocker(data, delta, wd); *wditem_ptr = - git_iterator_advance(workdir, &wd) ? NULL : wd; + git_iterator_advance(&wd, workdir) ? NULL : wd; return act; } } /* case 1 - handle wd item (if it matches pathspec) */ if (checkout_action_wd_only(data, workdir, wd, pathspec) < 0 || - git_iterator_advance(workdir, &wd) < 0) + git_iterator_advance(&wd, workdir) < 0) goto fail; *wditem_ptr = wd; @@ -501,7 +505,7 @@ static int checkout_action( if (cmp == 0) { /* case 4 */ act = checkout_action_with_wd(data, delta, wd); - *wditem_ptr = git_iterator_advance(workdir, &wd) ? NULL : wd; + *wditem_ptr = git_iterator_advance(&wd, workdir) ? NULL : wd; return act; } @@ -514,7 +518,7 @@ static int checkout_action( if (delta->status == GIT_DELTA_TYPECHANGE) { if (delta->old_file.mode == GIT_FILEMODE_TREE) { act = checkout_action_with_wd(data, delta, wd); - if (git_iterator_advance_into_directory(workdir, &wd) < 0) + if (git_iterator_advance_into(&wd, workdir) < 0) wd = NULL; *wditem_ptr = wd; return act; @@ -525,7 +529,7 @@ static int checkout_action( delta->old_file.mode == GIT_FILEMODE_COMMIT) { act = checkout_action_with_wd(data, delta, wd); - if (git_iterator_advance(workdir, &wd) < 0) + if (git_iterator_advance(&wd, workdir) < 0) wd = NULL; *wditem_ptr = wd; return act; @@ -554,7 +558,7 @@ static int checkout_remaining_wd_items( while (wd && !error) { if (!(error = checkout_action_wd_only(data, workdir, wd, spec))) - error = git_iterator_advance(workdir, &wd); + error = git_iterator_advance(&wd, workdir); } return error; @@ -578,7 +582,7 @@ static int checkout_get_actions( git_pathspec_init(&pathspec, &data->opts.paths, &pathpool) < 0) return -1; - if ((error = git_iterator_current(workdir, &wditem)) < 0) + if ((error = git_iterator_current(&wditem, workdir)) < 0) goto fail; deltas = &data->diff->deltas; @@ -610,7 +614,7 @@ static int checkout_get_actions( if (act & CHECKOUT_ACTION__CONFLICT) counts[CHECKOUT_ACTION__CONFLICT]++; } - + error = checkout_remaining_wd_items(data, workdir, wditem, &pathspec); if (error < 0) goto fail; @@ -665,11 +669,11 @@ static int buffer_to_file( giterr_set(GITERR_OS, "Could not write to '%s'", path); (void)p_close(fd); } else { - if ((error = p_fstat(fd, st)) < 0) - giterr_set(GITERR_OS, "Error while statting '%s'", path); - if ((error = p_close(fd)) < 0) giterr_set(GITERR_OS, "Error while closing '%s'", path); + + if ((error = p_stat(path, st)) < 0) + giterr_set(GITERR_OS, "Error while statting '%s'", path); } if (!error && @@ -1134,16 +1138,17 @@ static int checkout_data_init( if ((error = git_config_refresh(cfg)) < 0) goto cleanup; - if (git_iterator_inner_type(target) == GIT_ITERATOR_TYPE_INDEX) { - /* if we are iterating over the index, don't reload */ - data->index = git_iterator_index_get_index(target); + /* if we are checking out the index, don't reload, + * otherwise get index and force reload + */ + if ((data->index = git_iterator_get_index(target)) != NULL) { GIT_REFCOUNT_INC(data->index); } else { /* otherwise, grab and reload the index */ if ((error = git_repository_index(&data->index, data->repo)) < 0 || (error = git_index_read(data->index)) < 0) goto cleanup; - + /* clear the REUC when doing a tree or commit checkout */ git_index_reuc_clear(data->index); } @@ -1241,16 +1246,15 @@ int git_checkout_iterator( GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE; if ((error = git_iterator_reset(target, data.pfx, data.pfx)) < 0 || - (error = git_iterator_for_workdir_range( - &workdir, data.repo, iterflags, data.pfx, data.pfx)) < 0 || - (error = git_iterator_for_tree_range( + (error = git_iterator_for_workdir( + &workdir, data.repo, iterflags | GIT_ITERATOR_DONT_AUTOEXPAND, + data.pfx, data.pfx)) < 0 || + (error = git_iterator_for_tree( &baseline, data.opts.baseline, iterflags, data.pfx, data.pfx)) < 0) goto cleanup; - /* Handle case insensitivity for baseline if necessary */ - if (git_iterator_ignore_case(workdir) != git_iterator_ignore_case(baseline)) - if ((error = git_iterator_spoolandsort_push(baseline, true)) < 0) - goto cleanup; + /* Should not have case insensitivity mismatch */ + assert(git_iterator_ignore_case(workdir) == git_iterator_ignore_case(baseline)); /* Generate baseline-to-target diff which will include an entry for * every possible update that might need to be made. @@ -1321,7 +1325,7 @@ int git_checkout_index( return error; GIT_REFCOUNT_INC(index); - if (!(error = git_iterator_for_index(&index_i, index))) + if (!(error = git_iterator_for_index(&index_i, index, 0, NULL, NULL))) error = git_checkout_iterator(index_i, opts); git_iterator_free(index_i); @@ -1348,7 +1352,7 @@ int git_checkout_tree( return -1; } - if (!(error = git_iterator_for_tree(&tree_i, tree))) + if (!(error = git_iterator_for_tree(&tree_i, tree, 0, NULL, NULL))) error = git_checkout_iterator(tree_i, opts); git_iterator_free(tree_i); @@ -1369,7 +1373,7 @@ int git_checkout_head( return error; if (!(error = checkout_lookup_head_tree(&head, repo)) && - !(error = git_iterator_for_tree(&head_i, head))) + !(error = git_iterator_for_tree(&head_i, head, 0, NULL, NULL))) error = git_checkout_iterator(head_i, opts); git_iterator_free(head_i); diff --git a/src/commit.c b/src/commit.c index 29ce39107..e2d3d346b 100644 --- a/src/commit.c +++ b/src/commit.c @@ -20,7 +20,7 @@ static void clear_parents(git_commit *commit) { - unsigned int i; + size_t i; for (i = 0; i < commit->parent_ids.length; ++i) { git_oid *parent = git_vector_get(&commit->parent_ids, i); @@ -121,7 +121,7 @@ int git_commit_create( git_buf_free(&commit); if (update_ref != NULL) - return git_reference__update(repo, oid, update_ref); + return git_reference__update_terminal(repo, update_ref, oid); return 0; diff --git a/src/common.h b/src/common.h index e3a9e1984..02d9ce9b6 100644 --- a/src/common.h +++ b/src/common.h @@ -29,9 +29,10 @@ # include "win32/msvc-compat.h" # include "win32/mingw-compat.h" # include "win32/error.h" +# include "win32/version.h" # ifdef GIT_THREADS # include "win32/pthread.h" -#endif +# endif #else @@ -56,6 +57,12 @@ #define GITERR_CHECK_ALLOC(ptr) if (ptr == NULL) { return -1; } /** + * Check a return value and propogate result if non-zero. + */ +#define GITERR_CHECK_ERROR(code) \ + do { int _err = (code); if (_err < 0) return _err; } while (0) + +/** * Set the error message for this thread, formatting as needed. */ void giterr_set(int error_class, const char *string, ...); diff --git a/src/config.c b/src/config.c index d6aa3078c..5379b0ec5 100644 --- a/src/config.c +++ b/src/config.c @@ -36,7 +36,7 @@ static void file_internal_free(file_internal *internal) static void config_free(git_config *cfg) { - unsigned int i; + size_t i; file_internal *internal; for(i = 0; i < cfg->files.length; ++i){ @@ -284,7 +284,7 @@ int git_config_add_backend( int git_config_refresh(git_config *cfg) { int error = 0; - unsigned int i; + size_t i; for (i = 0; i < cfg->files.length && !error; ++i) { file_internal *internal = git_vector_get(&cfg->files, i); @@ -312,7 +312,7 @@ int git_config_foreach_match( void *payload) { int ret = 0; - unsigned int i; + size_t i; file_internal *internal; git_config_backend *file; @@ -362,6 +362,11 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value) git_config_backend *file; file_internal *internal; + if (!value) { + giterr_set(GITERR_CONFIG, "The value to set cannot be NULL"); + return -1; + } + internal = git_vector_get(&cfg->files, 0); file = internal->file; @@ -510,62 +515,48 @@ int git_config_set_multivar(git_config *cfg, const char *name, const char *regex return file->set_multivar(file, name, regexp, value); } -int git_config_find_global_r(git_buf *path) +static int git_config__find_file_to_path( + char *out, size_t outlen, int (*find)(git_buf *buf)) { - int error = git_futils_find_global_file(path, GIT_CONFIG_FILENAME); + int error = 0; + git_buf path = GIT_BUF_INIT; + + if ((error = find(&path)) < 0) + goto done; + if (path.size >= outlen) { + giterr_set(GITERR_NOMEMORY, "Buffer is too short for the path"); + error = GIT_EBUFS; + goto done; + } + + git_buf_copy_cstr(out, outlen, &path); + +done: + git_buf_free(&path); return error; } -int git_config_find_xdg_r(git_buf *path) +int git_config_find_global_r(git_buf *path) { - int error = git_futils_find_global_file(path, GIT_CONFIG_FILENAME_ALT); - - return error; + return git_futils_find_global_file(path, GIT_CONFIG_FILENAME_GLOBAL); } 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; - } + return git_config__find_file_to_path( + global_config_path, length, git_config_find_global_r); +} - git_buf_copy_cstr(global_config_path, length, &path); - git_buf_free(&path); - return 0; +int git_config_find_xdg_r(git_buf *path) +{ + return git_futils_find_xdg_file(path, GIT_CONFIG_FILENAME_XDG); } int git_config_find_xdg(char *xdg_config_path, size_t length) { - git_buf path = GIT_BUF_INIT; - int ret = git_config_find_xdg_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(xdg_config_path, length, &path); - git_buf_free(&path); - return 0; + return git_config__find_file_to_path( + xdg_config_path, length, git_config_find_xdg_r); } int git_config_find_system_r(git_buf *path) @@ -575,24 +566,8 @@ int git_config_find_system_r(git_buf *path) 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; + return git_config__find_file_to_path( + system_config_path, length, git_config_find_system_r); } int git_config_open_default(git_config **out) diff --git a/src/config.h b/src/config.h index db5ebb3b7..c43e47e82 100644 --- a/src/config.h +++ b/src/config.h @@ -12,10 +12,11 @@ #include "vector.h" #include "repository.h" -#define GIT_CONFIG_FILENAME ".gitconfig" -#define GIT_CONFIG_FILENAME_ALT ".config/git/config" -#define GIT_CONFIG_FILENAME_INREPO "config" #define GIT_CONFIG_FILENAME_SYSTEM "gitconfig" +#define GIT_CONFIG_FILENAME_GLOBAL ".gitconfig" +#define GIT_CONFIG_FILENAME_XDG "config" + +#define GIT_CONFIG_FILENAME_INREPO "config" #define GIT_CONFIG_FILE_MODE 0666 struct git_config { diff --git a/src/crlf.c b/src/crlf.c index 060d39d37..81268da83 100644 --- a/src/crlf.c +++ b/src/crlf.c @@ -9,9 +9,10 @@ #include "fileops.h" #include "hash.h" #include "filter.h" +#include "buf_text.h" #include "repository.h" - #include "git2/attr.h" +#include "git2/blob.h" struct crlf_attrs { int crlf_action; @@ -21,6 +22,8 @@ struct crlf_attrs { struct crlf_filter { git_filter f; struct crlf_attrs attrs; + git_repository *repo; + char path[GIT_FLEX_ARRAY]; }; static int check_crlf(const char *value) @@ -103,36 +106,46 @@ static int crlf_load_attributes(struct crlf_attrs *ca, git_repository *repo, con return -1; } -static int drop_crlf(git_buf *dest, const git_buf *source) +static int has_cr_in_index(git_filter *self) { - const char *scan = source->ptr, *next; - const char *scan_end = git_buf_cstr(source) + git_buf_len(source); + struct crlf_filter *filter = (struct crlf_filter *)self; + git_index *index; + const git_index_entry *entry; + git_blob *blob; + const void *blobcontent; + git_off_t blobsize; + bool found_cr; + + if (git_repository_index__weakptr(&index, filter->repo) < 0) { + giterr_clear(); + return false; + } - /* 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); + if (!(entry = git_index_get_bypath(index, filter->path, 0)) && + !(entry = git_index_get_bypath(index, filter->path, 1))) + return false; - /* Do not drop \r unless it is followed by \n */ - if (*(next + 1) != '\n') - git_buf_putc(dest, '\r'); + if (!S_ISREG(entry->mode)) /* don't crlf filter non-blobs */ + return true; - scan = next + 1; - } + if (git_blob_lookup(&blob, filter->repo, &entry->oid) < 0) + return false; - /* If there was no \r, then tell the library to skip this filter */ - if (scan == source->ptr) - return -1; + blobcontent = git_blob_rawcontent(blob); + blobsize = git_blob_rawsize(blob); + if (!git__is_sizet(blobsize)) + blobsize = (size_t)-1; + + found_cr = (blobcontent != NULL && + blobsize > 0 && + memchr(blobcontent, '\r', (size_t)blobsize) != NULL); - /* Copy remaining input into dest */ - git_buf_put(dest, scan, scan_end - scan); - return 0; + git_blob_free(blob); + return found_cr; } -static int crlf_apply_to_odb(git_filter *self, git_buf *dest, const git_buf *source) +static int crlf_apply_to_odb( + git_filter *self, git_buf *dest, const git_buf *source) { struct crlf_filter *filter = (struct crlf_filter *)self; @@ -162,40 +175,21 @@ static int crlf_apply_to_odb(git_filter *self, git_buf *dest, const git_buf *sou if (stats.cr != stats.crlf) return -1; -#if 0 - if (crlf_action == CRLF_GUESS) { + if (filter->attrs.crlf_action == GIT_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; + if (has_cr_in_index(self)) + return -1; } -#endif if (!stats.cr) return -1; } /* Actually drop the carriage returns */ - return drop_crlf(dest, source); -} - -static int convert_line_endings(git_buf *dest, const git_buf *source, const char *ending) -{ - const char *scan = git_buf_cstr(source), - *next, - *scan_end = git_buf_cstr(source) + git_buf_len(source); - - while ((next = memchr(scan, '\n', scan_end - scan)) != NULL) { - if (next > scan) - git_buf_put(dest, scan, next-scan); - git_buf_puts(dest, ending); - scan = next + 1; - } - - git_buf_put(dest, scan, scan_end - scan); - return 0; + return git_buf_text_crlf_to_lf(dest, source); } static const char *line_ending(struct crlf_filter *filter) @@ -238,26 +232,28 @@ line_ending_error: return NULL; } -static int crlf_apply_to_workdir(git_filter *self, git_buf *dest, const git_buf *source) +static int crlf_apply_to_workdir( + git_filter *self, git_buf *dest, const git_buf *source) { struct crlf_filter *filter = (struct crlf_filter *)self; const char *workdir_ending = NULL; - assert (self && dest && source); + assert(self && dest && source); /* Empty file? Nothing to do. */ if (git_buf_len(source) == 0) - return 0; + return -1; /* Determine proper line ending */ workdir_ending = line_ending(filter); - if (!workdir_ending) return -1; - - /* If the line ending is '\n', just copy the input */ - if (!strcmp(workdir_ending, "\n")) - return git_buf_puts(dest, git_buf_cstr(source)); + if (!workdir_ending) + return -1; + if (!strcmp("\n", workdir_ending)) /* do nothing for \n ending */ + return -1; - return convert_line_endings(dest, source, workdir_ending); + /* for now, only lf->crlf conversion is supported here */ + assert(!strcmp("\r\n", workdir_ending)); + return git_buf_text_lf_to_crlf(dest, source); } static int find_and_add_filter( @@ -266,6 +262,7 @@ static int find_and_add_filter( { struct crlf_attrs ca; struct crlf_filter *filter; + size_t pathlen; int error; /* Load gitattributes for the path */ @@ -293,22 +290,27 @@ static int find_and_add_filter( /* If we're good, we create a new filter object and push it * into the filters array */ - filter = git__malloc(sizeof(struct crlf_filter)); + pathlen = strlen(path); + filter = git__malloc(sizeof(struct crlf_filter) + pathlen + 1); GITERR_CHECK_ALLOC(filter); filter->f.apply = apply; filter->f.do_free = NULL; memcpy(&filter->attrs, &ca, sizeof(struct crlf_attrs)); + filter->repo = repo; + memcpy(filter->path, path, pathlen + 1); return git_vector_insert(filters, filter); } -int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const char *path) +int git_filter_add__crlf_to_odb( + git_vector *filters, git_repository *repo, const char *path) { return find_and_add_filter(filters, repo, path, &crlf_apply_to_odb); } -int git_filter_add__crlf_to_workdir(git_vector *filters, git_repository *repo, const char *path) +int git_filter_add__crlf_to_workdir( + git_vector *filters, git_repository *repo, const char *path) { return find_and_add_filter(filters, repo, path, &crlf_apply_to_workdir); } diff --git a/src/diff.c b/src/diff.c index 0861b13eb..7152683e7 100644 --- a/src/diff.c +++ b/src/diff.c @@ -12,6 +12,9 @@ #include "filter.h" #include "pathspec.h" +#define DIFF_FLAG_IS_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) != 0) +#define DIFF_FLAG_ISNT_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) == 0) + static git_diff_delta *diff_delta__alloc( git_diff_list *diff, git_delta_t status, @@ -29,7 +32,7 @@ static git_diff_delta *diff_delta__alloc( delta->new_file.path = delta->old_file.path; - if (diff->opts.flags & GIT_DIFF_REVERSE) { + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { switch (status) { case GIT_DELTA_ADDED: status = GIT_DELTA_DELETED; break; case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break; @@ -63,17 +66,22 @@ static int diff_delta__from_one( int notify_res; if (status == GIT_DELTA_IGNORED && - (diff->opts.flags & GIT_DIFF_INCLUDE_IGNORED) == 0) + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) return 0; if (status == GIT_DELTA_UNTRACKED && - (diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED)) + return 0; + + if (entry->mode == GIT_FILEMODE_COMMIT && + DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES)) return 0; if (!git_pathspec_match_path( &diff->pathspec, entry->path, - (diff->opts.flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH) != 0, - (diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0, &matched_pathspec)) + DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH), + DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE), + &matched_pathspec)) return 0; delta = diff_delta__alloc(diff, status, entry->path); @@ -124,10 +132,15 @@ static int diff_delta__from_two( int notify_res; if (status == GIT_DELTA_UNMODIFIED && - (diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED)) return 0; - if ((diff->opts.flags & GIT_DIFF_REVERSE) != 0) { + if (old_entry->mode == GIT_FILEMODE_COMMIT && + new_entry->mode == GIT_FILEMODE_COMMIT && + DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES)) + return 0; + + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { uint32_t temp_mode = old_mode; const git_index_entry *temp_entry = old_entry; old_entry = new_entry; @@ -149,7 +162,7 @@ static int diff_delta__from_two( delta->new_file.mode = new_mode; if (new_oid) { - if ((diff->opts.flags & GIT_DIFF_REVERSE) != 0) + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) git_oid_cpy(&delta->old_file.oid, new_oid); else git_oid_cpy(&delta->new_file.oid, new_oid); @@ -291,8 +304,11 @@ static git_diff_list *git_diff_list_alloc( * - diff.noprefix */ - if (opts == NULL) + if (opts == NULL) { + /* Make sure we default to 3 lines */ + diff->opts.context_lines = 3; return diff; + } memcpy(&diff->opts, opts, sizeof(git_diff_options)); @@ -313,14 +329,14 @@ static git_diff_list *git_diff_list_alloc( if (!diff->opts.old_prefix || !diff->opts.new_prefix) goto fail; - if (diff->opts.flags & GIT_DIFF_REVERSE) { + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { const char *swap = diff->opts.old_prefix; diff->opts.old_prefix = diff->opts.new_prefix; diff->opts.new_prefix = swap; } /* INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */ - if (diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES) + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES)) diff->opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE; return diff; @@ -449,8 +465,9 @@ static int maybe_modified( if (!git_pathspec_match_path( &diff->pathspec, oitem->path, - (diff->opts.flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH) != 0, - (diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0, &matched_pathspec)) + DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH), + DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE), + &matched_pathspec)) return 0; /* on platforms with no symlinks, preserve mode of existing symlinks */ @@ -475,7 +492,7 @@ static int maybe_modified( /* if basic type of file changed, then split into delete and add */ else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) { - if ((diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE) != 0) + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE)) status = GIT_DELTA_TYPECHANGE; else { if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 || @@ -509,13 +526,17 @@ static int maybe_modified( status = GIT_DELTA_UNMODIFIED; else if (S_ISGITLINK(nmode)) { + int err; git_submodule *sub; - if ((diff->opts.flags & GIT_DIFF_IGNORE_SUBMODULES) != 0) + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES)) status = GIT_DELTA_UNMODIFIED; - else if (git_submodule_lookup(&sub, diff->repo, nitem->path) < 0) - return -1; - else if (git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL) + else if ((err = git_submodule_lookup(&sub, diff->repo, nitem->path)) < 0) { + if (err == GIT_EEXISTS) + status = GIT_DELTA_UNMODIFIED; + else + return err; + } else if (git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL) status = GIT_DELTA_UNMODIFIED; else { unsigned int sm_status = 0; @@ -536,6 +557,11 @@ static int maybe_modified( } } + /* if mode is GITLINK and submodules are ignored, then skip */ + else if (S_ISGITLINK(nmode) && + DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES)) + status = GIT_DELTA_UNMODIFIED; + /* if we got here and decided that the files are modified, but we * haven't calculated the OID of the new item, then calculate it now */ @@ -546,7 +572,13 @@ static int maybe_modified( return -1; use_noid = &noid; } - if (omode == nmode && git_oid_equal(&oitem->oid, use_noid)) + + /* if oid matches, then mark unmodified (except submodules, where + * the filesystem content may be modified even if the oid still + * matches between the index and the workdir HEAD) + */ + if (omode == nmode && !S_ISGITLINK(omode) && + git_oid_equal(&oitem->oid, use_noid)) status = GIT_DELTA_UNMODIFIED; } @@ -619,19 +651,14 @@ int git_diff__from_iterators( if (!diff || diff_list_init_from_iterators(diff, old_iter, new_iter) < 0) goto fail; - if (diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) { - /* If either iterator does not have ignore_case set, then we will - * spool its data, sort it icase, and use that for the merge join - * with the other iterator which was icase sorted. This call is - * a no-op on an iterator that already matches "ignore_case". - */ - if (git_iterator_spoolandsort_push(old_iter, true) < 0 || - git_iterator_spoolandsort_push(new_iter, true) < 0) + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE)) { + if (git_iterator_set_ignore_case(old_iter, true) < 0 || + git_iterator_set_ignore_case(new_iter, true) < 0) goto fail; } - if (git_iterator_current(old_iter, &oitem) < 0 || - git_iterator_current(new_iter, &nitem) < 0) + if (git_iterator_current(&oitem, old_iter) < 0 || + git_iterator_current(&nitem, new_iter) < 0) goto fail; /* run iterators building diffs */ @@ -646,7 +673,7 @@ int git_diff__from_iterators( /* if we are generating TYPECHANGE records then check for that * instead of just generating a DELETE record */ - if ((diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES) != 0 && + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) && entry_is_prefixed(diff, nitem, oitem)) { /* this entry has become a tree! convert to TYPECHANGE */ @@ -661,14 +688,14 @@ int git_diff__from_iterators( * Unless RECURSE_UNTRACKED_DIRS is set, skip over them... */ if (S_ISDIR(nitem->mode) && - !(diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS)) + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) { - if (git_iterator_advance(new_iter, &nitem) < 0) + if (git_iterator_advance(&nitem, new_iter) < 0) goto fail; } } - if (git_iterator_advance(old_iter, &oitem) < 0) + if (git_iterator_advance(&oitem, old_iter) < 0) goto fail; } @@ -689,30 +716,48 @@ int git_diff__from_iterators( * it or if the user requested the contents of untracked * directories and it is not under an ignored directory. */ - bool recurse_untracked = + bool recurse_into_dir = (delta_type == GIT_DELTA_UNTRACKED && - (diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS) != 0); + DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) || + (delta_type == GIT_DELTA_IGNORED && + DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS)); /* do not advance into directories that contain a .git file */ - if (!contains_oitem && recurse_untracked) { + if (!contains_oitem && recurse_into_dir) { git_buf *full = NULL; - if (git_iterator_current_workdir_path(new_iter, &full) < 0) + if (git_iterator_current_workdir_path(&full, new_iter) < 0) goto fail; if (git_path_contains_dir(full, DOT_GIT)) - recurse_untracked = false; + recurse_into_dir = false; } - if (contains_oitem || recurse_untracked) { - /* 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 directory is ignored, remember ignore_prefix */ + if ((contains_oitem || recurse_into_dir) && + delta_type == GIT_DELTA_UNTRACKED && + git_iterator_current_is_ignored(new_iter)) + { + git_buf_sets(&ignore_prefix, nitem->path); + delta_type = GIT_DELTA_IGNORED; - if (git_iterator_advance_into_directory(new_iter, &nitem) < 0) - goto fail; + /* skip recursion if we've just learned this is ignored */ + if (DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS)) + recurse_into_dir = false; + } + + if (contains_oitem || recurse_into_dir) { + /* advance into directory */ + error = git_iterator_advance_into(&nitem, new_iter); + + /* if directory is empty, can't advance into it, so skip */ + if (error == GIT_ENOTFOUND) { + giterr_clear(); + error = git_iterator_advance(&nitem, new_iter); + git_buf_clear(&ignore_prefix); + } + + if (error < 0) + goto fail; continue; } } @@ -732,8 +777,9 @@ int git_diff__from_iterators( * 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) + else if (delta_type == GIT_DELTA_IGNORED && + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS)) { + if (git_iterator_advance(&nitem, new_iter) < 0) goto fail; continue; /* ignored parent directory, so skip completely */ } @@ -751,7 +797,7 @@ int git_diff__from_iterators( * instead of just generating an ADDED/UNTRACKED record */ if (delta_type != GIT_DELTA_IGNORED && - (diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES) != 0 && + DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) && contains_oitem) { /* this entry was prefixed with a tree - make TYPECHANGE */ @@ -762,7 +808,7 @@ int git_diff__from_iterators( } } - if (git_iterator_advance(new_iter, &nitem) < 0) + if (git_iterator_advance(&nitem, new_iter) < 0) goto fail; } @@ -772,11 +818,10 @@ int git_diff__from_iterators( else { assert(oitem && nitem && cmp == 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; + if (maybe_modified(old_iter, oitem, new_iter, nitem, diff) < 0 || + git_iterator_advance(&oitem, old_iter) < 0 || + git_iterator_advance(&nitem, new_iter) < 0) + goto fail; } } @@ -797,7 +842,7 @@ fail: git_iterator *a = NULL, *b = NULL; \ char *pfx = opts ? git_pathspec_prefix(&opts->pathspec) : NULL; \ GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); \ - if (!(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \ + if (!(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \ error = git_diff__from_iterators(diff, repo, a, b, opts); \ git__free(pfx); git_iterator_free(a); git_iterator_free(b); \ } while (0) @@ -814,8 +859,8 @@ int git_diff_tree_to_tree( assert(diff && repo); DIFF_FROM_ITERATORS( - git_iterator_for_tree_range(&a, old_tree, 0, pfx, pfx), - git_iterator_for_tree_range(&b, new_tree, 0, pfx, pfx) + git_iterator_for_tree(&a, old_tree, 0, pfx, pfx), + git_iterator_for_tree(&b, new_tree, 0, pfx, pfx) ); return error; @@ -836,8 +881,8 @@ int git_diff_tree_to_index( return error; DIFF_FROM_ITERATORS( - git_iterator_for_tree_range(&a, old_tree, 0, pfx, pfx), - git_iterator_for_index_range(&b, index, 0, pfx, pfx) + git_iterator_for_tree(&a, old_tree, 0, pfx, pfx), + git_iterator_for_index(&b, index, 0, pfx, pfx) ); return error; @@ -857,8 +902,9 @@ int git_diff_index_to_workdir( return error; DIFF_FROM_ITERATORS( - git_iterator_for_index_range(&a, index, 0, pfx, pfx), - git_iterator_for_workdir_range(&b, repo, 0, pfx, pfx) + git_iterator_for_index(&a, index, 0, pfx, pfx), + git_iterator_for_workdir( + &b, repo, GIT_ITERATOR_DONT_AUTOEXPAND, pfx, pfx) ); return error; @@ -876,8 +922,9 @@ int git_diff_tree_to_workdir( assert(diff && repo); DIFF_FROM_ITERATORS( - git_iterator_for_tree_range(&a, old_tree, 0, pfx, pfx), - git_iterator_for_workdir_range(&b, repo, 0, pfx, pfx) + git_iterator_for_tree(&a, old_tree, 0, pfx, pfx), + git_iterator_for_workdir( + &b, repo, GIT_ITERATOR_DONT_AUTOEXPAND, pfx, pfx) ); return error; diff --git a/src/diff_output.c b/src/diff_output.c index 209a6e017..e8dd5b317 100644 --- a/src/diff_output.c +++ b/src/diff_output.c @@ -12,6 +12,7 @@ #include <ctype.h> #include "fileops.h" #include "filter.h" +#include "buf_text.h" static int read_next_int(const char **str, int *value) { @@ -202,7 +203,7 @@ static void setup_xdiff_options( memset(param, 0, sizeof(xpparam_t)); cfg->ctxlen = - (!opts || !opts->context_lines) ? 3 : opts->context_lines; + (!opts) ? 3 : opts->context_lines; cfg->interhunkctxlen = (!opts) ? 0 : opts->interhunk_lines; @@ -299,7 +300,12 @@ static int get_workdir_sm_content( if ((error = git_submodule_lookup(&sm, ctxt->repo, file->path)) < 0 || (error = git_submodule_status(&sm_status, sm)) < 0) + { + /* GIT_EEXISTS means a "submodule" that has not been git added */ + if (error == GIT_EEXISTS) + error = 0; return error; + } /* update OID if we didn't have it previously */ if ((file->flags & GIT_DIFF_FLAG_VALID_OID) == 0) { @@ -330,6 +336,33 @@ static int get_workdir_sm_content( return 0; } +static int get_filtered( + git_map *map, git_file fd, git_diff_file *file, git_vector *filters) +{ + int error; + git_buf raw = GIT_BUF_INIT, filtered = GIT_BUF_INIT; + + if ((error = git_futils_readbuffer_fd(&raw, fd, (size_t)file->size)) < 0) + return error; + + if (!filters->length) + git_buf_swap(&filtered, &raw); + else + error = git_filters_apply(&filtered, &raw, filters); + + if (!error) { + map->len = git_buf_len(&filtered); + map->data = git_buf_detach(&filtered); + + file->flags |= GIT_DIFF_FLAG__FREE_DATA; + } + + git_buf_free(&raw); + git_buf_free(&filtered); + + return error; +} + static int get_workdir_content( diff_context *ctxt, git_diff_delta *delta, @@ -363,7 +396,7 @@ static int get_workdir_content( map->data = git__malloc(alloc_len); GITERR_CHECK_ALLOC(map->data); - read_len = p_readlink(path.ptr, map->data, (int)alloc_len); + read_len = p_readlink(path.ptr, map->data, alloc_len); if (read_len < 0) { giterr_set(GITERR_OS, "Failed to read symlink '%s'", file->path); error = -1; @@ -381,8 +414,8 @@ static int get_workdir_content( goto cleanup; } - if (!file->size) - file->size = git_futils_filesize(fd); + if (!file->size && !(file->size = git_futils_filesize(fd))) + goto close_and_cleanup; if ((error = diff_delta_is_binary_by_size(ctxt, delta, file)) < 0 || (delta->flags & GIT_DIFF_FLAG_BINARY) != 0) @@ -394,26 +427,12 @@ static int get_workdir_content( goto close_and_cleanup; if (error == 0) { /* note: git_filters_load returns filter count */ - if (!file->size) - goto close_and_cleanup; - error = git_futils_mmap_ro(map, fd, 0, (size_t)file->size); - file->flags |= GIT_DIFF_FLAG__UNMAP_DATA; - } else { - git_buf raw = GIT_BUF_INIT, filtered = GIT_BUF_INIT; - - if (!(error = git_futils_readbuffer_fd(&raw, fd, (size_t)file->size)) && - !(error = git_filters_apply(&filtered, &raw, &filters))) - { - map->len = git_buf_len(&filtered); - map->data = git_buf_detach(&filtered); - - file->flags |= GIT_DIFF_FLAG__FREE_DATA; - } - - git_buf_free(&raw); - git_buf_free(&filtered); + if (!error) + file->flags |= GIT_DIFF_FLAG__UNMAP_DATA; } + if (error != 0) + error = get_filtered(map, fd, file, &filters); close_and_cleanup: git_filters_free(&filters); diff --git a/src/diff_tform.c b/src/diff_tform.c index 958d2bfec..efcb19d95 100644 --- a/src/diff_tform.c +++ b/src/diff_tform.c @@ -174,16 +174,34 @@ static int find_similar__hashsig_for_file( void **out, const git_diff_file *f, const char *path, void *p) { git_hashsig_option_t opt = (git_hashsig_option_t)p; + int error = 0; + GIT_UNUSED(f); - return git_hashsig_create_fromfile((git_hashsig **)out, path, opt); + error = git_hashsig_create_fromfile((git_hashsig **)out, path, opt); + + if (error == GIT_EBUFS) { + error = 0; + giterr_clear(); + } + + return error; } static int find_similar__hashsig_for_buf( void **out, const git_diff_file *f, const char *buf, size_t len, void *p) { git_hashsig_option_t opt = (git_hashsig_option_t)p; + int error = 0; + GIT_UNUSED(f); - return git_hashsig_create((git_hashsig **)out, buf, len, opt); + error = git_hashsig_create((git_hashsig **)out, buf, len, opt); + + if (error == GIT_EBUFS) { + error = 0; + giterr_clear(); + } + + return error; } static void find_similar__hashsig_free(void *sig, void *payload) @@ -376,15 +394,20 @@ static int similarity_calc( git_buf_free(&path); } else { /* compute hashsig from blob buffer */ git_blob *blob = NULL; + git_off_t blobsize; /* TODO: add max size threshold a la diff? */ if ((error = git_blob_lookup(&blob, diff->repo, &file->oid)) < 0) return error; + blobsize = git_blob_rawsize(blob); + if (!git__is_sizet(blobsize)) /* ? what to do ? */ + blobsize = (size_t)-1; + error = opts->metric->buffer_signature( &cache[file_idx], file, git_blob_rawcontent(blob), - git_blob_rawsize(blob), opts->metric->payload); + (size_t)blobsize, opts->metric->payload); git_blob_free(blob); } @@ -414,6 +437,10 @@ static int similarity_measure( return -1; if (!cache[b_idx] && similarity_calc(diff, opts, b_idx, cache) < 0) return -1; + + /* some metrics may not wish to process this file (too big / too small) */ + if (!cache[a_idx] || !cache[b_idx]) + return 0; /* compare signatures */ if (opts->metric->similarity( diff --git a/src/errors.c b/src/errors.c index c5f0b3b59..e2629f69e 100644 --- a/src/errors.c +++ b/src/errors.c @@ -103,6 +103,7 @@ int giterr_set_regex(const regex_t *regex, int error_code) void giterr_clear(void) { + set_error(0, NULL); GIT_GLOBAL->last_error = NULL; errno = 0; diff --git a/src/fetchhead.c b/src/fetchhead.c index 6e8fb9fac..4dcebb857 100644 --- a/src/fetchhead.c +++ b/src/fetchhead.c @@ -16,7 +16,6 @@ #include "refs.h" #include "repository.h" - int git_fetchhead_ref_cmp(const void *a, const void *b) { const git_fetchhead_ref *one = (const git_fetchhead_ref *)a; diff --git a/src/fileops.c b/src/fileops.c index c1824e812..d6244711f 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -529,7 +529,7 @@ int git_futils_cleanupdir_r(const char *path) git_buf fullpath = GIT_BUF_INIT; futils__rmdir_data data; - if ((error = git_buf_put(&fullpath, path, strlen(path)) < 0)) + if ((error = git_buf_put(&fullpath, path, strlen(path))) < 0) goto clean_up; data.base = ""; @@ -558,75 +558,186 @@ clean_up: return error; } -int git_futils_find_system_file(git_buf *path, const char *filename) + +static int git_futils_guess_system_dirs(git_buf *out) { #ifdef GIT_WIN32 - // try to find git.exe/git.cmd on path - if (!win32_find_system_file_using_path(path, filename)) - return 0; + return git_win32__find_system_dirs(out); +#else + return git_buf_sets(out, "/etc"); +#endif +} - // try to find msysgit installation path using registry - if (!win32_find_system_file_using_registry(path, filename)) - return 0; +static int git_futils_guess_global_dirs(git_buf *out) +{ +#ifdef GIT_WIN32 + return git_win32__find_global_dirs(out); #else - if (git_buf_joinpath(path, "/etc", filename) < 0) - return -1; + return git_buf_sets(out, getenv("HOME")); +#endif +} - if (git_path_exists(path->ptr) == true) - return 0; +static int git_futils_guess_xdg_dirs(git_buf *out) +{ +#ifdef GIT_WIN32 + return git_win32__find_xdg_dirs(out); +#else + const char *env = NULL; + + if ((env = getenv("XDG_CONFIG_HOME")) != NULL) + return git_buf_joinpath(out, env, "git"); + else if ((env = getenv("HOME")) != NULL) + return git_buf_joinpath(out, env, ".config/git"); + + git_buf_clear(out); + return 0; #endif +} - git_buf_clear(path); - giterr_set(GITERR_OS, "The system file '%s' doesn't exist", filename); - return GIT_ENOTFOUND; +typedef int (*git_futils_dirs_guess_cb)(git_buf *out); + +static git_buf git_futils__dirs[GIT_FUTILS_DIR__MAX] = + { GIT_BUF_INIT, GIT_BUF_INIT, GIT_BUF_INIT }; + +static git_futils_dirs_guess_cb git_futils__dir_guess[GIT_FUTILS_DIR__MAX] = { + git_futils_guess_system_dirs, + git_futils_guess_global_dirs, + git_futils_guess_xdg_dirs, +}; + +static int git_futils_check_selector(git_futils_dir_t which) +{ + if (which < GIT_FUTILS_DIR__MAX) + return 0; + giterr_set(GITERR_INVALID, "config directory selector out of range"); + return -1; } -int git_futils_find_global_file(git_buf *path, const char *filename) +int git_futils_dirs_get(const git_buf **out, git_futils_dir_t which) { -#ifdef GIT_WIN32 - struct win32_path root; - static const wchar_t *tmpls[4] = { - L"%HOME%\\", - L"%HOMEDRIVE%%HOMEPATH%\\", - L"%USERPROFILE%\\", - NULL, - }; - const wchar_t **tmpl; - - for (tmpl = tmpls; *tmpl != NULL; tmpl++) { - /* try to expand environment variable, skipping if not set */ - if (win32_expand_path(&root, *tmpl) != 0 || root.path[0] == L'%') + assert(out); + + *out = NULL; + + GITERR_CHECK_ERROR(git_futils_check_selector(which)); + + if (!git_buf_len(&git_futils__dirs[which])) + GITERR_CHECK_ERROR( + git_futils__dir_guess[which](&git_futils__dirs[which])); + + *out = &git_futils__dirs[which]; + return 0; +} + +int git_futils_dirs_get_str(char *out, size_t outlen, git_futils_dir_t which) +{ + const git_buf *path = NULL; + + GITERR_CHECK_ERROR(git_futils_check_selector(which)); + GITERR_CHECK_ERROR(git_futils_dirs_get(&path, which)); + + if (!out || path->size >= outlen) { + giterr_set(GITERR_NOMEMORY, "Buffer is too short for the path"); + return GIT_EBUFS; + } + + git_buf_copy_cstr(out, outlen, path); + return 0; +} + +#define PATH_MAGIC "$PATH" + +int git_futils_dirs_set(git_futils_dir_t which, const char *search_path) +{ + const char *expand_path = NULL; + git_buf merge = GIT_BUF_INIT; + + GITERR_CHECK_ERROR(git_futils_check_selector(which)); + + if (search_path != NULL) + expand_path = strstr(search_path, PATH_MAGIC); + + /* init with default if not yet done and needed (ignoring error) */ + if ((!search_path || expand_path) && + !git_buf_len(&git_futils__dirs[which])) + git_futils__dir_guess[which](&git_futils__dirs[which]); + + /* if $PATH is not referenced, then just set the path */ + if (!expand_path) + return git_buf_sets(&git_futils__dirs[which], search_path); + + /* otherwise set to join(before $PATH, old value, after $PATH) */ + if (expand_path > search_path) + git_buf_set(&merge, search_path, expand_path - search_path); + + if (git_buf_len(&git_futils__dirs[which])) + git_buf_join(&merge, GIT_PATH_LIST_SEPARATOR, + merge.ptr, git_futils__dirs[which].ptr); + + expand_path += strlen(PATH_MAGIC); + if (*expand_path) + git_buf_join(&merge, GIT_PATH_LIST_SEPARATOR, merge.ptr, expand_path); + + git_buf_swap(&git_futils__dirs[which], &merge); + git_buf_free(&merge); + + return git_buf_oom(&git_futils__dirs[which]) ? -1 : 0; +} + +void git_futils_dirs_free(void) +{ + int i; + for (i = 0; i < GIT_FUTILS_DIR__MAX; ++i) + git_buf_free(&git_futils__dirs[i]); +} + +static int git_futils_find_in_dirlist( + git_buf *path, const char *name, git_futils_dir_t which, const char *label) +{ + size_t len; + const char *scan, *next = NULL; + const git_buf *syspath; + + GITERR_CHECK_ERROR(git_futils_dirs_get(&syspath, which)); + + for (scan = git_buf_cstr(syspath); scan; scan = next) { + for (next = strchr(scan, GIT_PATH_LIST_SEPARATOR); + next && next > scan && next[-1] == '\\'; + next = strchr(next + 1, GIT_PATH_LIST_SEPARATOR)) + /* find unescaped separator or end of string */; + + len = next ? (size_t)(next++ - scan) : strlen(scan); + if (!len) continue; - /* try to look up file under path */ - if (!win32_find_file(path, &root, filename)) + GITERR_CHECK_ERROR(git_buf_set(path, scan, len)); + GITERR_CHECK_ERROR(git_buf_joinpath(path, path->ptr, name)); + + if (git_path_exists(path->ptr)) return 0; } - giterr_set(GITERR_OS, "The global file '%s' doesn't exist", filename); git_buf_clear(path); - + giterr_set(GITERR_OS, "The %s file '%s' doesn't exist", label, name); return GIT_ENOTFOUND; -#else - const char *home = getenv("HOME"); - - if (home == NULL) { - giterr_set(GITERR_OS, "Global file lookup failed. " - "Cannot locate the user's home directory"); - return GIT_ENOTFOUND; - } +} - if (git_buf_joinpath(path, home, filename) < 0) - return -1; +int git_futils_find_system_file(git_buf *path, const char *filename) +{ + return git_futils_find_in_dirlist( + path, filename, GIT_FUTILS_DIR_SYSTEM, "system"); +} - if (git_path_exists(path->ptr) == false) { - giterr_set(GITERR_OS, "The global file '%s' doesn't exist", filename); - git_buf_clear(path); - return GIT_ENOTFOUND; - } +int git_futils_find_global_file(git_buf *path, const char *filename) +{ + return git_futils_find_in_dirlist( + path, filename, GIT_FUTILS_DIR_GLOBAL, "global"); +} - return 0; -#endif +int git_futils_find_xdg_file(git_buf *path, const char *filename) +{ + return git_futils_find_in_dirlist( + path, filename, GIT_FUTILS_DIR_XDG, "global/xdg"); } int git_futils_fake_symlink(const char *old, const char *new) diff --git a/src/fileops.h b/src/fileops.h index 7ba99d3d9..627a6923d 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -276,25 +276,72 @@ extern void git_futils_mmap_free(git_map *map); * * @param pathbuf buffer to write the full path into * @param filename name of file to find in the home directory - * @return - * - 0 if found; - * - GIT_ENOTFOUND if not found; - * - -1 on an unspecified OS related error. + * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error */ extern int git_futils_find_global_file(git_buf *path, const char *filename); /** + * Find an "XDG" file (i.e. one in user's XDG config path). + * + * @param pathbuf buffer to write the full path into + * @param filename name of file to find in the home directory + * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error + */ +extern int git_futils_find_xdg_file(git_buf *path, const char *filename); + +/** * Find a "system" file (i.e. one shared for all users of the system). * * @param pathbuf buffer to write the full path into * @param filename name of file to find in the home directory - * @return - * - 0 if found; - * - GIT_ENOTFOUND if not found; - * - -1 on an unspecified OS related error. + * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error */ extern int git_futils_find_system_file(git_buf *path, const char *filename); +typedef enum { + GIT_FUTILS_DIR_SYSTEM = 0, + GIT_FUTILS_DIR_GLOBAL = 1, + GIT_FUTILS_DIR_XDG = 2, + GIT_FUTILS_DIR__MAX = 3, +} git_futils_dir_t; + +/** + * Get the search path for global/system/xdg files + * + * @param out pointer to git_buf containing search path + * @param which which list of paths to return + * @return 0 on success, <0 on failure + */ +extern int git_futils_dirs_get(const git_buf **out, git_futils_dir_t which); + +/** + * Get search path into a preallocated buffer + * + * @param out String buffer to write into + * @param outlen Size of string buffer + * @param which Which search path to return + * @return 0 on success, GIT_EBUFS if out is too small, <0 on other failure + */ + +extern int git_futils_dirs_get_str( + char *out, size_t outlen, git_futils_dir_t which); + +/** + * Set search paths for global/system/xdg files + * + * The first occurrence of the magic string "$PATH" in the new value will + * be replaced with the old value of the search path. + * + * @param which Which search path to modify + * @param paths New search path (separated by GIT_PATH_LIST_SEPARATOR) + * @return 0 on success, <0 on failure (allocation error) + */ +extern int git_futils_dirs_set(git_futils_dir_t which, const char *paths); + +/** + * Release / reset all search paths + */ +extern void git_futils_dirs_free(void); /** * Create a "fake" symlink (text file containing the target path). diff --git a/src/filter.c b/src/filter.c index f0bfb7980..9f749dcbd 100644 --- a/src/filter.c +++ b/src/filter.c @@ -48,7 +48,8 @@ void git_filters_free(git_vector *filters) int git_filters_apply(git_buf *dest, git_buf *source, git_vector *filters) { - unsigned int i, src; + size_t i; + unsigned int src; git_buf *dbuffer[2]; dbuffer[0] = source; diff --git a/src/filter.h b/src/filter.h index 0ca71656b..42a44ebdb 100644 --- a/src/filter.h +++ b/src/filter.h @@ -9,7 +9,6 @@ #include "common.h" #include "buffer.h" -#include "buf_text.h" #include "git2/odb.h" #include "git2/repository.h" diff --git a/src/global.c b/src/global.c index 4d37fa1d2..b7fd8e257 100644 --- a/src/global.c +++ b/src/global.c @@ -7,7 +7,8 @@ #include "common.h" #include "global.h" #include "hash.h" -#include "git2/threads.h" +#include "fileops.h" +#include "git2/threads.h" #include "thread-utils.h" @@ -82,6 +83,7 @@ void git_threads_shutdown(void) /* Shut down any subsystems that have global state */ git_hash_global_shutdown(); + git_futils_dirs_free(); } git_global_st *git__global_state(void) @@ -139,6 +141,7 @@ void git_threads_shutdown(void) /* Shut down any subsystems that have global state */ git_hash_global_shutdown(); + git_futils_dirs_free(); } git_global_st *git__global_state(void) @@ -171,7 +174,9 @@ int git_threads_init(void) void git_threads_shutdown(void) { - /* noop */ + /* Shut down any subsystems that have global state */ + git_hash_global_shutdown(); + git_futils_dirs_free(); } git_global_st *git__global_state(void) diff --git a/src/graph.c b/src/graph.c index cb1727924..277f588ca 100644 --- a/src/graph.c +++ b/src/graph.c @@ -147,25 +147,25 @@ on_error: } int git_graph_ahead_behind(size_t *ahead, size_t *behind, git_repository *repo, - const git_oid *one, const git_oid *two) + const git_oid *local, const git_oid *upstream) { git_revwalk *walk; - git_commit_list_node *commit1, *commit2; + git_commit_list_node *commit_u, *commit_l; if (git_revwalk_new(&walk, repo) < 0) return -1; - commit2 = git_revwalk__commit_lookup(walk, two); - if (commit2 == NULL) + commit_u = git_revwalk__commit_lookup(walk, upstream); + if (commit_u == NULL) goto on_error; - commit1 = git_revwalk__commit_lookup(walk, one); - if (commit1 == NULL) + commit_l = git_revwalk__commit_lookup(walk, local); + if (commit_l == NULL) goto on_error; - if (mark_parents(walk, commit1, commit2) < 0) + if (mark_parents(walk, commit_l, commit_u) < 0) goto on_error; - if (ahead_behind(commit1, commit2, ahead, behind) < 0) + if (ahead_behind(commit_l, commit_u, ahead, behind) < 0) goto on_error; git_revwalk_free(walk); diff --git a/src/hashsig.c b/src/hashsig.c index e9c5164a4..3a75aaaed 100644 --- a/src/hashsig.c +++ b/src/hashsig.c @@ -6,6 +6,7 @@ */ #include "hashsig.h" #include "fileops.h" +#include "util.h" typedef uint32_t hashsig_t; typedef uint64_t hashsig_state; @@ -19,7 +20,7 @@ typedef uint64_t hashsig_state; #define HASHSIG_HEAP_SIZE ((1 << 7) - 1) -typedef int (GIT_STDLIB_CALL *hashsig_cmp)(const void *a, const void *b); +typedef int (*hashsig_cmp)(const void *a, const void *b, void *); typedef struct { int size, asize; @@ -53,15 +54,17 @@ static void hashsig_heap_init(hashsig_heap *h, hashsig_cmp cmp) h->cmp = cmp; } -static int GIT_STDLIB_CALL hashsig_cmp_max(const void *a, const void *b) +static int hashsig_cmp_max(const void *a, const void *b, void *payload) { hashsig_t av = *(const hashsig_t *)a, bv = *(const hashsig_t *)b; + GIT_UNUSED(payload); return (av < bv) ? -1 : (av > bv) ? 1 : 0; } -static int GIT_STDLIB_CALL hashsig_cmp_min(const void *a, const void *b) +static int hashsig_cmp_min(const void *a, const void *b, void *payload) { hashsig_t av = *(const hashsig_t *)a, bv = *(const hashsig_t *)b; + GIT_UNUSED(payload); return (av > bv) ? -1 : (av < bv) ? 1 : 0; } @@ -69,7 +72,7 @@ static void hashsig_heap_up(hashsig_heap *h, int el) { int parent_el = HEAP_PARENT_OF(el); - while (el > 0 && h->cmp(&h->values[parent_el], &h->values[el]) > 0) { + while (el > 0 && h->cmp(&h->values[parent_el], &h->values[el], NULL) > 0) { hashsig_t t = h->values[el]; h->values[el] = h->values[parent_el]; h->values[parent_el] = t; @@ -92,10 +95,10 @@ static void hashsig_heap_down(hashsig_heap *h, int el) lv = h->values[lel]; rv = h->values[rel]; - if (h->cmp(&v, &lv) < 0 && h->cmp(&v, &rv) < 0) + if (h->cmp(&v, &lv, NULL) < 0 && h->cmp(&v, &rv, NULL) < 0) break; - swapel = (h->cmp(&lv, &rv) < 0) ? lel : rel; + swapel = (h->cmp(&lv, &rv, NULL) < 0) ? lel : rel; h->values[el] = h->values[swapel]; h->values[swapel] = v; @@ -107,13 +110,13 @@ static void hashsig_heap_down(hashsig_heap *h, int el) static void hashsig_heap_sort(hashsig_heap *h) { /* only need to do this at the end for signature comparison */ - qsort(h->values, h->size, sizeof(hashsig_t), h->cmp); + git__qsort_r(h->values, h->size, sizeof(hashsig_t), h->cmp, NULL); } static void hashsig_heap_insert(hashsig_heap *h, hashsig_t val) { /* if heap is full, pop top if new element should replace it */ - if (h->size == h->asize && h->cmp(&val, &h->values[0]) > 0) { + if (h->size == h->asize && h->cmp(&val, &h->values[0], NULL) > 0) { h->size--; h->values[0] = h->values[h->size]; hashsig_heap_down(h, 0); @@ -343,7 +346,7 @@ static int hashsig_heap_compare(const hashsig_heap *a, const hashsig_heap *b) /* hash heaps are sorted - just look for overlap vs total */ for (i = 0, j = 0; i < a->size && j < b->size; ) { - cmp = a->cmp(&a->values[i], &b->values[j]); + cmp = a->cmp(&a->values[i], &b->values[j], NULL); if (cmp < 0) ++i; diff --git a/src/ignore.c b/src/ignore.c index 5edc5b65b..17779522c 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -1,11 +1,10 @@ #include "git2/ignore.h" #include "ignore.h" +#include "attr.h" #include "path.h" #include "config.h" #define GIT_IGNORE_INTERNAL "[internal]exclude" -#define GIT_IGNORE_FILE_INREPO "info/exclude" -#define GIT_IGNORE_FILE ".gitignore" #define GIT_IGNORE_DEFAULT_RULES ".\n..\n.git\n" diff --git a/src/ignore.h b/src/ignore.h index 5a15afcca..5af8e8e7d 100644 --- a/src/ignore.h +++ b/src/ignore.h @@ -9,6 +9,11 @@ #include "repository.h" #include "vector.h" +#include "attr_file.h" + +#define GIT_IGNORE_FILE ".gitignore" +#define GIT_IGNORE_FILE_INREPO "info/exclude" +#define GIT_IGNORE_FILE_XDG "ignore" /* The git_ignores structure maintains three sets of ignores: * - internal ignores diff --git a/src/index.c b/src/index.c index 4deafd77f..6290ec4e8 100644 --- a/src/index.c +++ b/src/index.c @@ -317,7 +317,7 @@ void git_index_free(git_index *index) void git_index_clear(git_index *index) { - unsigned int i; + size_t i; assert(index); @@ -786,7 +786,7 @@ int git_index_remove(git_index *index, const char *path, int stage) if (entry != NULL) git_tree_cache_invalidate_path(index->tree, entry->path); - error = git_vector_remove(&index->entries, (unsigned int)position); + error = git_vector_remove(&index->entries, position); if (!error) index_entry_free(entry); @@ -1129,7 +1129,7 @@ int git_index_reuc_remove(git_index *index, size_t position) git_vector_sort(&index->reuc); reuc = git_vector_get(&index->reuc, position); - error = git_vector_remove(&index->reuc, (unsigned int)position); + error = git_vector_remove(&index->reuc, position); if (!error) index_entry_reuc_free(reuc); @@ -1679,54 +1679,3 @@ git_repository *git_index_owner(const git_index *index) { return INDEX_OWNER(index); } - -int git_index_read_tree_match( - git_index *index, git_tree *tree, git_strarray *strspec) -{ -#if 0 - git_iterator *iter = NULL; - const git_index_entry *entry; - char *pfx = NULL; - git_vector pathspec = GIT_VECTOR_INIT; - git_pool pathpool = GIT_POOL_INIT_STRINGPOOL; -#endif - - if (!git_pathspec_is_interesting(strspec)) - return git_index_read_tree(index, tree); - - return git_index_read_tree(index, tree); - -#if 0 - /* The following loads the matches into the index, but doesn't - * erase obsoleted entries (e.g. you load a blob at "a/b" which - * should obsolete a blob at "a/b/c/d" since b is no longer a tree) - */ - - if (git_pathspec_init(&pathspec, strspec, &pathpool) < 0) - return -1; - - pfx = git_pathspec_prefix(strspec); - - if ((error = git_iterator_for_tree_range(&iter, tree, pfx, pfx)) < 0 || - (error = git_iterator_current(iter, &entry)) < 0) - goto cleanup; - - while (entry != NULL) { - if (git_pathspec_match_path( - &pathspec, entry->path, false, false, NULL) && - (error = git_index_add(index, entry)) < 0) - goto cleanup; - - if ((error = git_iterator_advance(iter, &entry)) < 0) - goto cleanup; - } - -cleanup: - git_iterator_free(iter); - git_pathspec_free(&pathspec); - git_pool_clear(&pathpool); - git__free(pfx); - - return error; -#endif -} diff --git a/src/index.h b/src/index.h index 2beaa6375..9498907b6 100644 --- a/src/index.h +++ b/src/index.h @@ -50,7 +50,4 @@ extern int git_index_entry__cmp_icase(const void *a, const void *b); extern void git_index__set_ignore_case(git_index *index, bool ignore_case); -extern int git_index_read_tree_match( - git_index *index, git_tree *tree, git_strarray *strspec); - #endif diff --git a/src/indexer.c b/src/indexer.c index c7e142baf..2cfbd3a5a 100644 --- a/src/indexer.c +++ b/src/indexer.c @@ -415,6 +415,8 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz } if (!idx->parsed_header) { + unsigned int total_objects; + if ((unsigned)idx->pack->mwf.size < sizeof(hdr)) return 0; @@ -427,20 +429,24 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz /* for now, limit to 2^32 objects */ assert(idx->nr_objects == (size_t)((unsigned int)idx->nr_objects)); + if (idx->nr_objects == (size_t)((unsigned int)idx->nr_objects)) + total_objects = (unsigned int)idx->nr_objects; + else + total_objects = UINT_MAX; idx->pack->idx_cache = git_oidmap_alloc(); GITERR_CHECK_ALLOC(idx->pack->idx_cache); idx->pack->has_cache = 1; - if (git_vector_init(&idx->objects, (unsigned int)idx->nr_objects, objects_cmp) < 0) + if (git_vector_init(&idx->objects, total_objects, objects_cmp) < 0) return -1; - if (git_vector_init(&idx->deltas, (unsigned int)(idx->nr_objects / 2), NULL) < 0) + if (git_vector_init(&idx->deltas, total_objects / 2, NULL) < 0) return -1; stats->received_objects = 0; processed = stats->indexed_objects = 0; - stats->total_objects = (unsigned int)idx->nr_objects; + stats->total_objects = total_objects; do_progress_callback(idx, stats); } diff --git a/src/iterator.c b/src/iterator.c index 8ad639d6b..805a3c987 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -14,26 +14,45 @@ #define ITERATOR_SET_CB(P,NAME_LC) do { \ (P)->cb.current = NAME_LC ## _iterator__current; \ - (P)->cb.at_end = NAME_LC ## _iterator__at_end; \ (P)->cb.advance = NAME_LC ## _iterator__advance; \ + (P)->cb.advance_into = NAME_LC ## _iterator__advance_into; \ (P)->cb.seek = NAME_LC ## _iterator__seek; \ (P)->cb.reset = NAME_LC ## _iterator__reset; \ + (P)->cb.at_end = NAME_LC ## _iterator__at_end; \ (P)->cb.free = NAME_LC ## _iterator__free; \ } while (0) -#define ITERATOR_BASE_INIT(P,NAME_LC,NAME_UC) do { \ +#define ITERATOR_CASE_FLAGS \ + (GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_IGNORE_CASE) + +#define ITERATOR_BASE_INIT(P,NAME_LC,NAME_UC,REPO) do { \ (P) = git__calloc(1, sizeof(NAME_LC ## _iterator)); \ GITERR_CHECK_ALLOC(P); \ (P)->base.type = GIT_ITERATOR_TYPE_ ## NAME_UC; \ - (P)->base.cb = &(P)->cb; \ + (P)->base.cb = &(P)->cb; \ ITERATOR_SET_CB(P,NAME_LC); \ + (P)->base.repo = (REPO); \ (P)->base.start = start ? git__strdup(start) : NULL; \ (P)->base.end = end ? git__strdup(end) : NULL; \ if ((start && !(P)->base.start) || (end && !(P)->base.end)) { \ git__free(P); return -1; } \ (P)->base.prefixcomp = git__prefixcmp; \ + (P)->base.flags = flags & ~ITERATOR_CASE_FLAGS; \ + if ((P)->base.flags & GIT_ITERATOR_DONT_AUTOEXPAND) \ + (P)->base.flags |= GIT_ITERATOR_INCLUDE_TREES; \ } while (0) +#define iterator__flag(I,F) ((((git_iterator *)(I))->flags & GIT_ITERATOR_ ## F) != 0) +#define iterator__ignore_case(I) iterator__flag(I,IGNORE_CASE) +#define iterator__include_trees(I) iterator__flag(I,INCLUDE_TREES) +#define iterator__dont_autoexpand(I) iterator__flag(I,DONT_AUTOEXPAND) +#define iterator__do_autoexpand(I) !iterator__flag(I,DONT_AUTOEXPAND) + +#define iterator__end(I) ((git_iterator *)(I))->end +#define iterator__past_end(I,PATH) \ + (iterator__end(I) && ((git_iterator *)(I))->prefixcomp((PATH),iterator__end(I)) > 0) + + static int iterator__reset_range( git_iterator *iter, const char *start, const char *end) { @@ -54,7 +73,7 @@ static int iterator__reset_range( return 0; } -static int iterator_update_ignore_case( +static int iterator__update_ignore_case( git_iterator *iter, git_iterator_flag_t flags) { @@ -76,42 +95,46 @@ static int iterator_update_ignore_case( else if (ignore_case == 0) iter->flags = (iter->flags & ~GIT_ITERATOR_IGNORE_CASE); - iter->prefixcomp = ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0) ? + iter->prefixcomp = iterator__ignore_case(iter) ? git__prefixcmp_icase : git__prefixcmp; return error; } -static int empty_iterator__no_item( - git_iterator *iter, const git_index_entry **entry) +GIT_INLINE(void) iterator__clear_entry(const git_index_entry **entry) +{ + if (entry) *entry = NULL; +} + + +static int empty_iterator__noop(const git_index_entry **e, git_iterator *i) { - GIT_UNUSED(iter); - *entry = NULL; + GIT_UNUSED(i); + iterator__clear_entry(e); return 0; } -static int empty_iterator__at_end(git_iterator *iter) +static int empty_iterator__seek(git_iterator *i, const char *p) { - GIT_UNUSED(iter); - return 1; + GIT_UNUSED(i); GIT_UNUSED(p); + return -1; } -static int empty_iterator__reset( - git_iterator *iter, const char *start, const char *end) +static int empty_iterator__reset(git_iterator *i, const char *s, const char *e) { - GIT_UNUSED(iter); GIT_UNUSED(start); GIT_UNUSED(end); + GIT_UNUSED(i); GIT_UNUSED(s); GIT_UNUSED(e); return 0; } -static int empty_iterator__seek(git_iterator *iter, const char *prefix) +static int empty_iterator__at_end(git_iterator *i) { - GIT_UNUSED(iter); GIT_UNUSED(prefix); - return -1; + GIT_UNUSED(i); + return 1; } -static void empty_iterator__free(git_iterator *iter) +static void empty_iterator__free(git_iterator *i) { - GIT_UNUSED(iter); + GIT_UNUSED(i); } typedef struct { @@ -119,332 +142,430 @@ typedef struct { git_iterator_callbacks cb; } empty_iterator; -int git_iterator_for_nothing(git_iterator **iter, git_iterator_flag_t flags) +int git_iterator_for_nothing( + git_iterator **iter, + git_iterator_flag_t flags, + const char *start, + const char *end) { - empty_iterator *i = git__calloc(1, sizeof(empty_iterator)); - GITERR_CHECK_ALLOC(i); + empty_iterator *i; - i->base.type = GIT_ITERATOR_TYPE_EMPTY; - i->base.cb = &i->cb; - i->base.flags = flags; - i->cb.current = empty_iterator__no_item; - i->cb.at_end = empty_iterator__at_end; - i->cb.advance = empty_iterator__no_item; - i->cb.seek = empty_iterator__seek; - i->cb.reset = empty_iterator__reset; - i->cb.free = empty_iterator__free; +#define empty_iterator__current empty_iterator__noop +#define empty_iterator__advance empty_iterator__noop +#define empty_iterator__advance_into empty_iterator__noop - *iter = (git_iterator *)i; + ITERATOR_BASE_INIT(i, empty, EMPTY, NULL); + if ((flags & GIT_ITERATOR_IGNORE_CASE) != 0) + i->base.flags |= GIT_ITERATOR_IGNORE_CASE; + + *iter = (git_iterator *)i; return 0; } +typedef struct tree_iterator_entry tree_iterator_entry; +struct tree_iterator_entry { + tree_iterator_entry *parent; + const git_tree_entry *te; + git_tree *tree; +}; + typedef struct tree_iterator_frame tree_iterator_frame; struct tree_iterator_frame { - tree_iterator_frame *next, *prev; - git_tree *tree; - char *start; + tree_iterator_frame *up, *down; + + size_t n_entries; /* items in this frame */ + size_t current; /* start of currently active range in frame */ + size_t next; /* start of next range in frame */ + + const char *start; size_t startlen; - size_t index; - void **icase_map; - void *icase_data[GIT_FLEX_ARRAY]; + + tree_iterator_entry *entries[GIT_FLEX_ARRAY]; }; typedef struct { git_iterator base; git_iterator_callbacks cb; - tree_iterator_frame *stack, *tail; + tree_iterator_frame *head, *root; + git_pool pool; git_index_entry entry; git_buf path; + int path_ambiguities; bool path_has_filename; + int (*strncomp)(const char *a, const char *b, size_t sz); } tree_iterator; -GIT_INLINE(const git_tree_entry *)tree_iterator__tree_entry(tree_iterator *ti) -{ - tree_iterator_frame *tf = ti->stack; - - if (tf->index >= git_tree_entrycount(tf->tree)) - return NULL; - - return git_tree_entry_byindex( - tf->tree, tf->icase_map ? (size_t)tf->icase_map[tf->index] : tf->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; + + if (git_tree_entry__is_tree(te) && git_buf_putc(&ti->path, '/') < 0) + return NULL; + ti->path_has_filename = true; } return ti->path.ptr; } -static void tree_iterator__free_frame(tree_iterator_frame *tf) +static void tree_iterator__rewrite_filename(tree_iterator *ti) { - if (!tf) - return; + tree_iterator_entry *scan = ti->head->entries[ti->head->current]; + ssize_t strpos = ti->path.size; + const git_tree_entry *te; - git_tree_free(tf->tree); - tf->tree = NULL; + if (strpos && ti->path.ptr[strpos - 1] == '/') + strpos--; - git__free(tf); + for (; scan && (te = scan->te); scan = scan->parent) { + strpos -= te->filename_len; + memcpy(&ti->path.ptr[strpos], te->filename, te->filename_len); + strpos -= 1; /* separator */ + } } -static bool tree_iterator__pop_frame(tree_iterator *ti) +static int tree_iterator__te_cmp( + const git_tree_entry *a, + const git_tree_entry *b, + int (*compare)(const char *, const char *, size_t)) { - tree_iterator_frame *tf = ti->stack; - - /* don't free the initial tree/frame */ - if (!tf->next) - return false; + return git_path_cmp( + a->filename, a->filename_len, a->attr == GIT_FILEMODE_TREE, + b->filename, b->filename_len, b->attr == GIT_FILEMODE_TREE, + compare); +} - ti->stack = tf->next; - ti->stack->prev = NULL; +static int tree_iterator__ci_cmp(const void *a, const void *b, void *p) +{ + const tree_iterator_entry *ae = a, *be = b; + int cmp = tree_iterator__te_cmp(ae->te, be->te, git__strncasecmp); - tree_iterator__free_frame(tf); + if (!cmp) { + /* stabilize sort order among equivalent names */ + if (!ae->parent->te || !be->parent->te) + cmp = tree_iterator__te_cmp(ae->te, be->te, git__strncmp); + else + cmp = tree_iterator__ci_cmp(ae->parent, be->parent, p); + } - return true; + return cmp; } -static int tree_iterator__to_end(tree_iterator *ti) +static int tree_iterator__search_cmp(const void *key, const void *val, void *p) { - while (tree_iterator__pop_frame(ti)) /* pop all */; - ti->stack->index = git_tree_entrycount(ti->stack->tree); - return 0; + const tree_iterator_frame *tf = key; + const git_tree_entry *te = ((tree_iterator_entry *)val)->te; + + return git_path_cmp( + tf->start, tf->startlen, false, + te->filename, te->filename_len, te->attr == GIT_FILEMODE_TREE, + ((tree_iterator *)p)->strncomp); } -static int tree_iterator__current( - git_iterator *self, const git_index_entry **entry) +static int tree_iterator__set_next(tree_iterator *ti, tree_iterator_frame *tf) { - tree_iterator *ti = (tree_iterator *)self; - const git_tree_entry *te = tree_iterator__tree_entry(ti); + int error; + const git_tree_entry *te, *last = NULL; - if (entry) - *entry = NULL; + tf->next = tf->current; - if (te == NULL) - return 0; + for (; tf->next < tf->n_entries; tf->next++, last = te) { + te = tf->entries[tf->next]->te; - ti->entry.mode = te->attr; - git_oid_cpy(&ti->entry.oid, &te->oid); + if (last && tree_iterator__te_cmp(last, te, ti->strncomp)) + break; - ti->entry.path = tree_iterator__current_filename(ti, te); - if (ti->entry.path == NULL) - return -1; + /* load trees for items in [current,next) range */ + if (git_tree_entry__is_tree(te) && + (error = git_tree_lookup( + &tf->entries[tf->next]->tree, ti->base.repo, &te->oid)) < 0) + return error; + } - if (ti->base.end && ti->base.prefixcomp(ti->entry.path, ti->base.end) > 0) - return tree_iterator__to_end(ti); + if (tf->next > tf->current + 1) + ti->path_ambiguities++; - if (entry) - *entry = &ti->entry; + if (last && !tree_iterator__current_filename(ti, last)) + return -1; return 0; } -static int tree_iterator__at_end(git_iterator *self) +GIT_INLINE(bool) tree_iterator__at_tree(tree_iterator *ti) { - return (tree_iterator__tree_entry((tree_iterator *)self) == NULL); + return (ti->head->current < ti->head->n_entries && + ti->head->entries[ti->head->current]->tree != NULL); } -static int tree_iterator__icase_map_cmp(const void *a, const void *b, void *data) +static int tree_iterator__push_frame(tree_iterator *ti) { - git_tree *tree = data; - const git_tree_entry *te1 = git_tree_entry_byindex(tree, (size_t)a); - const git_tree_entry *te2 = git_tree_entry_byindex(tree, (size_t)b); + int error = 0; + tree_iterator_frame *head = ti->head, *tf = NULL; + size_t i, n_entries = 0; - return te1 ? (te2 ? git_tree_entry_icmp(te1, te2) : 1) : -1; -} + if (head->current >= head->n_entries || !head->entries[head->current]->tree) + return 0; -static int tree_iterator__frame_start_icmp(const void *key, const void *el) -{ - const tree_iterator_frame *tf = (const tree_iterator_frame *)key; - const git_tree_entry *te = git_tree_entry_byindex(tf->tree, (size_t)el); - size_t minlen = min(tf->startlen, te->filename_len); + for (i = head->current; i < head->next; ++i) + n_entries += git_tree_entrycount(head->entries[i]->tree); - return git__strncasecmp(tf->start, te->filename, minlen); -} + tf = git__calloc(sizeof(tree_iterator_frame) + + n_entries * sizeof(tree_iterator_entry *), 1); + GITERR_CHECK_ALLOC(tf); -static void tree_iterator__frame_seek_start(tree_iterator_frame *tf) -{ - if (!tf->start) - tf->index = 0; - else if (!tf->icase_map) - tf->index = git_tree__prefix_position(tf->tree, tf->start); - else { - if (!git__bsearch( - tf->icase_map, git_tree_entrycount(tf->tree), - tf, tree_iterator__frame_start_icmp, &tf->index)) - { - while (tf->index > 0) { - /* move back while previous entry is still prefixed */ - if (tree_iterator__frame_start_icmp( - tf, (const void *)(tf->index - 1))) - break; - tf->index--; - } + tf->n_entries = n_entries; + + tf->up = head; + head->down = tf; + ti->head = tf; + + for (i = head->current, n_entries = 0; i < head->next; ++i) { + git_tree *tree = head->entries[i]->tree; + size_t j, max_j = git_tree_entrycount(tree); + + for (j = 0; j < max_j; ++j) { + tree_iterator_entry *entry = git_pool_malloc(&ti->pool, 1); + GITERR_CHECK_ALLOC(entry); + + entry->parent = head->entries[i]; + entry->te = git_tree_entry_byindex(tree, j); + entry->tree = NULL; + + tf->entries[n_entries++] = entry; } } + + /* if ignore_case, sort entries case insensitively */ + if (iterator__ignore_case(ti)) + git__tsort_r( + (void **)tf->entries, tf->n_entries, tree_iterator__ci_cmp, tf); + + /* pick tf->current based on "start" (or start at zero) */ + if (head->startlen > 0) { + git__bsearch_r((void **)tf->entries, tf->n_entries, head, + tree_iterator__search_cmp, ti, &tf->current); + + while (tf->current && + !tree_iterator__search_cmp(head, tf->entries[tf->current-1], ti)) + tf->current--; + + if ((tf->start = strchr(head->start, '/')) != NULL) { + tf->start++; + tf->startlen = strlen(tf->start); + } + } + + ti->path_has_filename = false; + + if ((error = tree_iterator__set_next(ti, tf)) < 0) + return error; + + /* autoexpand as needed */ + if (!iterator__include_trees(ti) && tree_iterator__at_tree(ti)) + return tree_iterator__push_frame(ti); + + return 0; } -static tree_iterator_frame *tree_iterator__alloc_frame( - tree_iterator *ti, git_tree *tree, char *start) +static bool tree_iterator__move_to_next( + tree_iterator *ti, tree_iterator_frame *tf) { - size_t i, max_i = git_tree_entrycount(tree); - tree_iterator_frame *tf = - git__calloc(1, sizeof(tree_iterator_frame) + max_i * sizeof(void *)); - if (!tf) - return NULL; + if (tf->next > tf->current + 1) + ti->path_ambiguities--; - tf->tree = tree; + if (!tf->up) { /* at root */ + tf->current = tf->next; + return false; + } - if (start && *start) { - tf->start = start; - tf->startlen = strlen(start); + for (; tf->current < tf->next; tf->current++) { + git_tree_free(tf->entries[tf->current]->tree); + tf->entries[tf->current]->tree = NULL; } - if (!max_i) - return tf; + return (tf->current < tf->n_entries); +} - if ((ti->base.flags & GIT_ITERATOR_IGNORE_CASE) != 0) { - tf->icase_map = tf->icase_data; +static bool tree_iterator__pop_frame(tree_iterator *ti, bool final) +{ + tree_iterator_frame *tf = ti->head; - for (i = 0; i < max_i; ++i) - tf->icase_map[i] = (void *)i; + if (!tf->up) + return false; - git__tsort_r( - tf->icase_map, max_i, tree_iterator__icase_map_cmp, tf->tree); + ti->head = tf->up; + ti->head->down = NULL; + + tree_iterator__move_to_next(ti, tf); + + if (!final) { /* if final, don't bother to clean up */ + git_pool_free_array(&ti->pool, tf->n_entries, (void **)tf->entries); + git_buf_rtruncate_at_char(&ti->path, '/'); } - tree_iterator__frame_seek_start(tf); + git__free(tf); - return tf; + return true; } -static int tree_iterator__expand_tree(tree_iterator *ti) +static int tree_iterator__pop_all(tree_iterator *ti, bool to_end, bool final) { - int error; - git_tree *subtree; - const git_tree_entry *te = tree_iterator__tree_entry(ti); - tree_iterator_frame *tf; - char *relpath; + while (tree_iterator__pop_frame(ti, final)) /* pop to root */; - while (te != NULL && git_tree_entry__is_tree(te)) { - if (git_buf_joinpath(&ti->path, ti->path.ptr, te->filename) < 0) - return -1; + if (!final) { + ti->head->current = to_end ? ti->head->n_entries : 0; + ti->path_ambiguities = 0; + git_buf_clear(&ti->path); + } - /* check that we have not passed the range end */ - if (ti->base.end != NULL && - ti->base.prefixcomp(ti->path.ptr, ti->base.end) > 0) - return tree_iterator__to_end(ti); + return 0; +} - if ((error = git_tree_lookup(&subtree, ti->base.repo, &te->oid)) < 0) - return error; +static int tree_iterator__current( + const git_index_entry **entry, git_iterator *self) +{ + tree_iterator *ti = (tree_iterator *)self; + tree_iterator_frame *tf = ti->head; + const git_tree_entry *te; - relpath = NULL; + iterator__clear_entry(entry); - /* apply range start to new frame if relevant */ - if (ti->stack->start && - ti->base.prefixcomp(ti->stack->start, te->filename) == 0) - { - if (ti->stack->start[te->filename_len] == '/') - relpath = ti->stack->start + te->filename_len + 1; - } + if (tf->current >= tf->n_entries) + return 0; + te = tf->entries[tf->current]->te; - if ((tf = tree_iterator__alloc_frame(ti, subtree, relpath)) == NULL) - return -1; + ti->entry.mode = te->attr; + git_oid_cpy(&ti->entry.oid, &te->oid); - tf->next = ti->stack; - ti->stack = tf; - tf->next->prev = tf; + ti->entry.path = tree_iterator__current_filename(ti, te); + GITERR_CHECK_ALLOC(ti->entry.path); - te = tree_iterator__tree_entry(ti); - } + if (ti->path_ambiguities > 0) + tree_iterator__rewrite_filename(ti); + + if (iterator__past_end(ti, ti->entry.path)) + return tree_iterator__pop_all(ti, true, false); + + if (entry) + *entry = &ti->entry; return 0; } -static int tree_iterator__advance( - git_iterator *self, const git_index_entry **entry) +static int tree_iterator__advance_into( + const git_index_entry **entry, git_iterator *self) { int error = 0; tree_iterator *ti = (tree_iterator *)self; - const git_tree_entry *te = NULL; - if (entry != NULL) - *entry = NULL; + iterator__clear_entry(entry); - if (ti->path_has_filename) { - git_buf_rtruncate_at_char(&ti->path, '/'); - ti->path_has_filename = false; - } + if (tree_iterator__at_tree(ti) && + !(error = tree_iterator__push_frame(ti))) + error = tree_iterator__current(entry, self); - while (1) { - ++ti->stack->index; + return error; +} - if ((te = tree_iterator__tree_entry(ti)) != NULL) - break; +static int tree_iterator__advance( + const git_index_entry **entry, git_iterator *self) +{ + int error; + tree_iterator *ti = (tree_iterator *)self; + tree_iterator_frame *tf = ti->head; + + iterator__clear_entry(entry); + + if (tf->current > tf->n_entries) + return 0; - if (!tree_iterator__pop_frame(ti)) - break; /* no frames left to pop */ + if (iterator__do_autoexpand(ti) && iterator__include_trees(ti) && + tree_iterator__at_tree(ti)) + return tree_iterator__advance_into(entry, self); + if (ti->path_has_filename) { git_buf_rtruncate_at_char(&ti->path, '/'); + ti->path_has_filename = false; } - if (te && git_tree_entry__is_tree(te)) - error = tree_iterator__expand_tree(ti); + /* scan forward and up, advancing in frame or popping frame when done */ + while (!tree_iterator__move_to_next(ti, tf) && + tree_iterator__pop_frame(ti, false)) + tf = ti->head; - if (!error) - error = tree_iterator__current(self, entry); + /* find next and load trees */ + if ((error = tree_iterator__set_next(ti, tf)) < 0) + return error; - return error; + /* deal with include_trees / auto_expand as needed */ + if (!iterator__include_trees(ti) && tree_iterator__at_tree(ti)) + return tree_iterator__advance_into(entry, self); + + return tree_iterator__current(entry, self); } 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 */ + GIT_UNUSED(self); GIT_UNUSED(prefix); return -1; } -static void tree_iterator__free(git_iterator *self) +static int tree_iterator__reset( + git_iterator *self, const char *start, const char *end) { tree_iterator *ti = (tree_iterator *)self; - while (tree_iterator__pop_frame(ti)) /* pop all */; + tree_iterator__pop_all(ti, false, false); + + if (iterator__reset_range(self, start, end) < 0) + return -1; - tree_iterator__free_frame(ti->stack); - ti->stack = ti->tail = NULL; + return tree_iterator__push_frame(ti); /* re-expand root tree */ +} - git_buf_free(&ti->path); +static int tree_iterator__at_end(git_iterator *self) +{ + tree_iterator *ti = (tree_iterator *)self; + return (ti->head->current >= ti->head->n_entries); } -static int tree_iterator__reset( - git_iterator *self, const char *start, const char *end) +static void tree_iterator__free(git_iterator *self) { tree_iterator *ti = (tree_iterator *)self; - while (tree_iterator__pop_frame(ti)) /* pop all */; + tree_iterator__pop_all(ti, true, false); - if (iterator__reset_range(self, start, end) < 0) - return -1; + git_tree_free(ti->head->entries[0]->tree); + git__free(ti->head); + git_pool_clear(&ti->pool); + git_buf_free(&ti->path); +} - /* reset start position */ - tree_iterator__frame_seek_start(ti->stack); +static int tree_iterator__create_root_frame(tree_iterator *ti, git_tree *tree) +{ + size_t sz = sizeof(tree_iterator_frame) + sizeof(tree_iterator_entry); + tree_iterator_frame *root = git__calloc(sz, sizeof(char)); + GITERR_CHECK_ALLOC(root); - git_buf_clear(&ti->path); - ti->path_has_filename = false; + root->n_entries = 1; + root->next = 1; + root->start = ti->base.start; + root->startlen = root->start ? strlen(root->start) : 0; + root->entries[0] = git_pool_mallocz(&ti->pool, 1); + GITERR_CHECK_ALLOC(root->entries[0]); + root->entries[0]->tree = tree; + + ti->head = ti->root = root; - return tree_iterator__expand_tree(ti); + return 0; } -int git_iterator_for_tree_range( +int git_iterator_for_tree( git_iterator **iter, git_tree *tree, git_iterator_flag_t flags, @@ -455,21 +576,20 @@ int git_iterator_for_tree_range( tree_iterator *ti; if (tree == NULL) - return git_iterator_for_nothing(iter, flags); + return git_iterator_for_nothing(iter, flags, start, end); if ((error = git_tree__dup(&tree, tree)) < 0) return error; - ITERATOR_BASE_INIT(ti, tree, TREE); - - ti->base.repo = git_tree_owner(tree); + ITERATOR_BASE_INIT(ti, tree, TREE, git_tree_owner(tree)); - if ((error = iterator_update_ignore_case((git_iterator *)ti, flags)) < 0) + if ((error = iterator__update_ignore_case((git_iterator *)ti, flags)) < 0) goto fail; + ti->strncomp = iterator__ignore_case(ti) ? git__strncasecmp : git__strncmp; - ti->stack = ti->tail = tree_iterator__alloc_frame(ti, tree, ti->base.start); - - if ((error = tree_iterator__expand_tree(ti)) < 0) + if ((error = git_pool_init(&ti->pool, sizeof(tree_iterator_entry),0)) < 0 || + (error = tree_iterator__create_root_frame(ti, tree)) < 0 || + (error = tree_iterator__push_frame(ti)) < 0) /* expand root now */ goto fail; *iter = (git_iterator *)ti; @@ -486,14 +606,94 @@ typedef struct { git_iterator_callbacks cb; git_index *index; size_t current; + /* when not in autoexpand mode, use these to represent "tree" state */ + git_buf partial; + size_t partial_pos; + char restore_terminator; + git_index_entry tree_entry; } index_iterator; +static const git_index_entry *index_iterator__index_entry(index_iterator *ii) +{ + const git_index_entry *ie = git_index_get_byindex(ii->index, ii->current); + + if (ie != NULL && iterator__past_end(ii, ie->path)) { + ii->current = git_index_entrycount(ii->index); + ie = NULL; + } + + return ie; +} + +static const git_index_entry *index_iterator__skip_conflicts(index_iterator *ii) +{ + const git_index_entry *ie; + + while ((ie = index_iterator__index_entry(ii)) != NULL && + git_index_entry_stage(ie) != 0) + ii->current++; + + return ie; +} + +static void index_iterator__next_prefix_tree(index_iterator *ii) +{ + const char *slash; + + if (!iterator__include_trees(ii)) + return; + + slash = strchr(&ii->partial.ptr[ii->partial_pos], '/'); + + if (slash != NULL) { + ii->partial_pos = (slash - ii->partial.ptr) + 1; + ii->restore_terminator = ii->partial.ptr[ii->partial_pos]; + ii->partial.ptr[ii->partial_pos] = '\0'; + } else { + ii->partial_pos = ii->partial.size; + } + + if (index_iterator__index_entry(ii) == NULL) + ii->partial_pos = ii->partial.size; +} + +static int index_iterator__first_prefix_tree(index_iterator *ii) +{ + const git_index_entry *ie = index_iterator__skip_conflicts(ii); + const char *scan, *prior, *slash; + + if (!ie || !iterator__include_trees(ii)) + return 0; + + /* find longest common prefix with prior index entry */ + for (scan = slash = ie->path, prior = ii->partial.ptr; + *scan && *scan == *prior; ++scan, ++prior) + if (*scan == '/') + slash = scan; + + if (git_buf_sets(&ii->partial, ie->path) < 0) + return -1; + + ii->partial_pos = (slash - ie->path) + 1; + index_iterator__next_prefix_tree(ii); + + return 0; +} + +#define index_iterator__at_tree(I) \ + (iterator__include_trees(I) && (I)->partial_pos < (I)->partial.size) + static int index_iterator__current( - git_iterator *self, const git_index_entry **entry) + const git_index_entry **entry, git_iterator *self) { index_iterator *ii = (index_iterator *)self; const git_index_entry *ie = git_index_get_byindex(ii->index, ii->current); + if (ie != NULL && index_iterator__at_tree(ii)) { + ii->tree_entry.path = ii->partial.ptr; + ie = &ii->tree_entry; + } + if (entry) *entry = ie; @@ -506,47 +706,59 @@ static int index_iterator__at_end(git_iterator *self) return (ii->current >= git_index_entrycount(ii->index)); } -static void index_iterator__skip_conflicts( - index_iterator *ii) +static int index_iterator__advance( + const git_index_entry **entry, git_iterator *self) { + index_iterator *ii = (index_iterator *)self; size_t entrycount = git_index_entrycount(ii->index); const git_index_entry *ie; - while (ii->current < entrycount) { - ie = git_index_get_byindex(ii->index, ii->current); + if (index_iterator__at_tree(ii)) { + if (iterator__do_autoexpand(ii)) { + ii->partial.ptr[ii->partial_pos] = ii->restore_terminator; + index_iterator__next_prefix_tree(ii); + } else { + /* advance to sibling tree (i.e. find entry with new prefix) */ + while (ii->current < entrycount) { + ii->current++; + + if (!(ie = git_index_get_byindex(ii->index, ii->current)) || + ii->base.prefixcomp(ie->path, ii->partial.ptr) != 0) + break; + } - if (ie == NULL || - (ii->base.end != NULL && - ii->base.prefixcomp(ie->path, ii->base.end) > 0)) { - ii->current = entrycount; - break; + if (index_iterator__first_prefix_tree(ii) < 0) + return -1; } + } else { + if (ii->current < entrycount) + ii->current++; - if (git_index_entry_stage(ie) == 0) - break; - - ii->current++; + if (index_iterator__first_prefix_tree(ii) < 0) + return -1; } + + return index_iterator__current(entry, self); } -static int index_iterator__advance( - git_iterator *self, const git_index_entry **entry) +static int index_iterator__advance_into( + const git_index_entry **entry, git_iterator *self) { index_iterator *ii = (index_iterator *)self; + const git_index_entry *ie = git_index_get_byindex(ii->index, ii->current); - if (ii->current < git_index_entrycount(ii->index)) - ii->current++; - - index_iterator__skip_conflicts(ii); + if (ie != NULL && index_iterator__at_tree(ii)) { + if (ii->restore_terminator) + ii->partial.ptr[ii->partial_pos] = ii->restore_terminator; + index_iterator__next_prefix_tree(ii); + } - return index_iterator__current(self, entry); + return index_iterator__current(entry, self); } static int index_iterator__seek(git_iterator *self, const char *prefix) { - GIT_UNUSED(self); - GIT_UNUSED(prefix); - /* find last item before prefix */ + GIT_UNUSED(self); GIT_UNUSED(prefix); return -1; } @@ -554,11 +766,31 @@ static int index_iterator__reset( git_iterator *self, const char *start, const char *end) { index_iterator *ii = (index_iterator *)self; + const git_index_entry *ie; + if (iterator__reset_range(self, start, end) < 0) return -1; + ii->current = ii->base.start ? git_index__prefix_position(ii->index, ii->base.start) : 0; - index_iterator__skip_conflicts(ii); + + if ((ie = index_iterator__skip_conflicts(ii)) == NULL) + return 0; + + if (git_buf_sets(&ii->partial, ie->path) < 0) + return -1; + + ii->partial_pos = 0; + + if (ii->base.start) { + size_t startlen = strlen(ii->base.start); + + ii->partial_pos = (startlen > ii->partial.size) ? + ii->partial.size : startlen; + } + + index_iterator__next_prefix_tree(ii); + return 0; } @@ -567,9 +799,11 @@ static void index_iterator__free(git_iterator *self) index_iterator *ii = (index_iterator *)self; git_index_free(ii->index); ii->index = NULL; + + git_buf_free(&ii->partial); } -int git_iterator_for_index_range( +int git_iterator_for_index( git_iterator **iter, git_index *index, git_iterator_flag_t flags, @@ -578,18 +812,19 @@ int git_iterator_for_index_range( { index_iterator *ii; - GIT_UNUSED(flags); + ITERATOR_BASE_INIT(ii, index, INDEX, git_index_owner(index)); - ITERATOR_BASE_INIT(ii, index, INDEX); - - ii->base.repo = git_index_owner(index); if (index->ignore_case) { ii->base.flags |= GIT_ITERATOR_IGNORE_CASE; ii->base.prefixcomp = git__prefixcmp_icase; } + ii->index = index; GIT_REFCOUNT_INC(index); + git_buf_init(&ii->partial, 0); + ii->tree_entry.mode = GIT_FILEMODE_TREE; + index_iterator__reset((git_iterator *)ii, NULL, NULL); *iter = (git_iterator *)ii; @@ -598,6 +833,8 @@ int git_iterator_for_index_range( } +#define WORKDIR_MAX_DEPTH 100 + typedef struct workdir_iterator_frame workdir_iterator_frame; struct workdir_iterator_frame { workdir_iterator_frame *next; @@ -609,12 +846,12 @@ typedef struct { git_iterator base; git_iterator_callbacks cb; workdir_iterator_frame *stack; - int (*entrycmp)(const void *pfx, const void *item); git_ignores ignores; git_index_entry entry; git_buf path; size_t root_len; int is_ignored; + int depth; } workdir_iterator; GIT_INLINE(bool) path_is_dotgit(const git_path_with_stat *ps) @@ -643,7 +880,7 @@ static workdir_iterator_frame *workdir_iterator__alloc_frame( { workdir_iterator_frame *wf = git__calloc(1, sizeof(workdir_iterator_frame)); git_vector_cmp entry_compare = CASESELECT( - (wi->base.flags & GIT_ITERATOR_IGNORE_CASE) != 0, + iterator__ignore_case(wi), git_path_with_stat_cmp_icase, git_path_with_stat_cmp); if (wf == NULL) @@ -670,16 +907,11 @@ static void workdir_iterator__free_frame(workdir_iterator_frame *wf) static int workdir_iterator__update_entry(workdir_iterator *wi); -static int workdir_iterator__entry_cmp_case(const void *pfx, const void *item) -{ - const git_path_with_stat *ps = item; - return git__prefixcmp((const char *)pfx, ps->path); -} - -static int workdir_iterator__entry_cmp_icase(const void *pfx, const void *item) +static int workdir_iterator__entry_cmp(const void *i, const void *item) { + const workdir_iterator *wi = (const workdir_iterator *)i; const git_path_with_stat *ps = item; - return git__prefixcmp_icase((const char *)pfx, ps->path); + return wi->base.prefixcomp(wi->base.start, ps->path); } static void workdir_iterator__seek_frame_start( @@ -690,7 +922,7 @@ static void workdir_iterator__seek_frame_start( if (wi->base.start) git_vector_bsearch2( - &wf->index, &wf->entries, wi->entrycmp, wi->base.start); + &wf->index, &wf->entries, workdir_iterator__entry_cmp, wi); else wf->index = 0; @@ -701,12 +933,13 @@ static void workdir_iterator__seek_frame_start( static int workdir_iterator__expand_dir(workdir_iterator *wi) { int error; - workdir_iterator_frame *wf = workdir_iterator__alloc_frame(wi); + workdir_iterator_frame *wf; + + wf = workdir_iterator__alloc_frame(wi); GITERR_CHECK_ALLOC(wf); error = git_path_dirload_with_stat( - wi->path.ptr, wi->root_len, - (wi->base.flags & GIT_ITERATOR_IGNORE_CASE) != 0, + wi->path.ptr, wi->root_len, iterator__ignore_case(wi), wi->base.start, wi->base.end, &wf->entries); if (error < 0 || wf->entries.length == 0) { @@ -714,6 +947,13 @@ static int workdir_iterator__expand_dir(workdir_iterator *wi) return GIT_ENOTFOUND; } + if (++(wi->depth) > WORKDIR_MAX_DEPTH) { + giterr_set(GITERR_REPOSITORY, + "Working directory is too deep (%d)", wi->depth); + workdir_iterator__free_frame(wf); + return -1; + } + workdir_iterator__seek_frame_start(wi, wf); /* only push new ignores if this is not top level directory */ @@ -729,10 +969,11 @@ static int workdir_iterator__expand_dir(workdir_iterator *wi) } static int workdir_iterator__current( - git_iterator *self, const git_index_entry **entry) + const git_index_entry **entry, git_iterator *self) { workdir_iterator *wi = (workdir_iterator *)self; - *entry = (wi->entry.path == NULL) ? NULL : &wi->entry; + if (entry) + *entry = (wi->entry.path == NULL) ? NULL : &wi->entry; return 0; } @@ -741,21 +982,56 @@ static int workdir_iterator__at_end(git_iterator *self) return (((workdir_iterator *)self)->entry.path == NULL); } +static int workdir_iterator__advance_into( + const git_index_entry **entry, git_iterator *iter) +{ + int error = 0; + workdir_iterator *wi = (workdir_iterator *)iter; + + iterator__clear_entry(entry); + + /* workdir iterator will allow you to explicitly advance into a + * commit/submodule (as well as a tree) to avoid some cases where an + * entry is mislabeled as a submodule in the working directory + */ + if (wi->entry.path != NULL && + (wi->entry.mode == GIT_FILEMODE_TREE || + wi->entry.mode == GIT_FILEMODE_COMMIT)) + /* returns GIT_ENOTFOUND if the directory is empty */ + error = workdir_iterator__expand_dir(wi); + + if (!error && entry) + error = workdir_iterator__current(entry, iter); + + return error; +} + static int workdir_iterator__advance( - git_iterator *self, const git_index_entry **entry) + const git_index_entry **entry, git_iterator *self) { - int error; + int error = 0; workdir_iterator *wi = (workdir_iterator *)self; workdir_iterator_frame *wf; git_path_with_stat *next; + /* given include_trees & autoexpand, we might have to go into a tree */ + if (iterator__do_autoexpand(wi) && + wi->entry.path != NULL && + wi->entry.mode == GIT_FILEMODE_TREE) + { + error = workdir_iterator__advance_into(entry, self); + + /* continue silently past empty directories if autoexpanding */ + if (error != GIT_ENOTFOUND) + return error; + giterr_clear(); + error = 0; + } + if (entry != NULL) *entry = NULL; - if (wi->entry.path == NULL) - return 0; - - while (1) { + while (wi->entry.path != NULL) { wf = wi->stack; next = git_vector_get(&wf->entries, ++wf->index); @@ -774,6 +1050,7 @@ static int workdir_iterator__advance( } wi->stack = wf->next; + wi->depth--; workdir_iterator__free_frame(wf); git_ignore__pop_dir(&wi->ignores); } @@ -781,7 +1058,7 @@ static int workdir_iterator__advance( error = workdir_iterator__update_entry(wi); if (!error && entry != NULL) - error = workdir_iterator__current(self, entry); + error = workdir_iterator__current(entry, self); return error; } @@ -807,6 +1084,7 @@ static int workdir_iterator__reset( workdir_iterator__free_frame(wf); git_ignore__pop_dir(&wi->ignores); } + wi->depth = 0; if (iterator__reset_range(self, start, end) < 0) return -1; @@ -832,6 +1110,7 @@ static void workdir_iterator__free(git_iterator *self) static int workdir_iterator__update_entry(workdir_iterator *wi) { + int error = 0; git_path_with_stat *ps = git_vector_get(&wi->stack->entries, wi->stack->index); @@ -841,19 +1120,18 @@ static int workdir_iterator__update_entry(workdir_iterator *wi) if (!ps) return 0; + /* skip over .git entries */ + if (path_is_dotgit(ps)) + return workdir_iterator__advance(NULL, (git_iterator *)wi); + if (git_buf_put(&wi->path, ps->path, ps->path_len) < 0) return -1; - if (wi->base.end && - wi->base.prefixcomp(wi->path.ptr + wi->root_len, wi->base.end) > 0) + if (iterator__past_end(wi, wi->path.ptr + wi->root_len)) return 0; wi->entry.path = ps->path; - /* skip over .git entries */ - if (path_is_dotgit(ps)) - return workdir_iterator__advance((git_iterator *)wi, NULL); - wi->is_ignored = -1; git_index_entry__init_from_stat(&wi->entry, &ps->st); @@ -867,26 +1145,34 @@ static int workdir_iterator__update_entry(workdir_iterator *wi) return 0; } + /* if this isn't a tree, then we're done */ + if (wi->entry.mode != GIT_FILEMODE_TREE) + return 0; + /* detect submodules */ - if (S_ISDIR(wi->entry.mode)) { - int res = git_submodule_lookup(NULL, wi->base.repo, wi->entry.path); - bool 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; - } + error = git_submodule_lookup(NULL, wi->base.repo, wi->entry.path); + if (error == GIT_ENOTFOUND) + giterr_clear(); + + if (error == GIT_EEXISTS) /* if contains .git, treat as untracked submod */ + error = 0; + + /* if submodule, mark as GITLINK and remove trailing slash */ + if (!error) { + 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; } - return 0; + if (iterator__include_trees(wi)) + return 0; + + return workdir_iterator__advance(NULL, (git_iterator *)wi); } -int git_iterator_for_workdir_range( +int git_iterator_for_workdir( git_iterator **iter, git_repository *repo, git_iterator_flag_t flags, @@ -899,13 +1185,12 @@ int git_iterator_for_workdir_range( assert(iter && repo); if ((error = git_repository__ensure_not_bare( - repo, "scan working directory")) < 0) + repo, "scan working directory")) < 0) return error; - ITERATOR_BASE_INIT(wi, workdir, WORKDIR); - wi->base.repo = repo; + ITERATOR_BASE_INIT(wi, workdir, WORKDIR, repo); - if ((error = iterator_update_ignore_case((git_iterator *)wi, flags)) < 0) + if ((error = iterator__update_ignore_case((git_iterator *)wi, flags)) < 0) goto fail; if (git_buf_sets(&wi->path, git_repository_workdir(repo)) < 0 || @@ -915,10 +1200,7 @@ int git_iterator_for_workdir_range( git__free(wi); return -1; } - wi->root_len = wi->path.size; - wi->entrycmp = (wi->base.flags & GIT_ITERATOR_IGNORE_CASE) != 0 ? - workdir_iterator__entry_cmp_icase : workdir_iterator__entry_cmp_case; if ((error = workdir_iterator__expand_dir(wi)) < 0) { if (error != GIT_ENOTFOUND) @@ -935,161 +1217,6 @@ fail: } -typedef struct { - /* replacement callbacks */ - git_iterator_callbacks cb; - /* original iterator values */ - git_iterator_callbacks *orig; - git_iterator_type_t orig_type; - /* spoolandsort data */ - git_vector entries; - git_pool entry_pool; - git_pool string_pool; - size_t position; -} spoolandsort_callbacks; - -static int spoolandsort_iterator__current( - git_iterator *self, const git_index_entry **entry) -{ - spoolandsort_callbacks *scb = (spoolandsort_callbacks *)self->cb; - - *entry = (const git_index_entry *) - git_vector_get(&scb->entries, scb->position); - - return 0; -} - -static int spoolandsort_iterator__at_end(git_iterator *self) -{ - spoolandsort_callbacks *scb = (spoolandsort_callbacks *)self->cb; - - return 0 == scb->entries.length || scb->entries.length - 1 <= scb->position; -} - -static int spoolandsort_iterator__advance( - git_iterator *self, const git_index_entry **entry) -{ - spoolandsort_callbacks *scb = (spoolandsort_callbacks *)self->cb; - - *entry = (const git_index_entry *) - git_vector_get(&scb->entries, ++scb->position); - - return 0; -} - -static int spoolandsort_iterator__seek(git_iterator *self, const char *prefix) -{ - GIT_UNUSED(self); - GIT_UNUSED(prefix); - - return -1; -} - -static int spoolandsort_iterator__reset( - git_iterator *self, const char *start, const char *end) -{ - spoolandsort_callbacks *scb = (spoolandsort_callbacks *)self->cb; - - GIT_UNUSED(start); GIT_UNUSED(end); - - scb->position = 0; - - return 0; -} - -static void spoolandsort_iterator__free_callbacks(spoolandsort_callbacks *scb) -{ - git_pool_clear(&scb->string_pool); - git_pool_clear(&scb->entry_pool); - git_vector_free(&scb->entries); - git__free(scb); -} - -void git_iterator_spoolandsort_pop(git_iterator *self) -{ - spoolandsort_callbacks *scb = (spoolandsort_callbacks *)self->cb; - - if (self->type != GIT_ITERATOR_TYPE_SPOOLANDSORT) - return; - - self->cb = scb->orig; - self->type = scb->orig_type; - self->flags ^= GIT_ITERATOR_IGNORE_CASE; - - spoolandsort_iterator__free_callbacks(scb); -} - -static void spoolandsort_iterator__free(git_iterator *self) -{ - git_iterator_spoolandsort_pop(self); - self->cb->free(self); -} - -int git_iterator_spoolandsort_push(git_iterator *iter, bool ignore_case) -{ - const git_index_entry *item; - spoolandsort_callbacks *scb; - int (*entrycomp)(const void *a, const void *b); - - if (((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0) == (ignore_case != 0)) - return 0; - - if (iter->type == GIT_ITERATOR_TYPE_EMPTY) { - iter->flags = (iter->flags ^ GIT_ITERATOR_IGNORE_CASE); - return 0; - } - - scb = git__calloc(1, sizeof(spoolandsort_callbacks)); - GITERR_CHECK_ALLOC(scb); - - ITERATOR_SET_CB(scb,spoolandsort); - - scb->orig = iter->cb; - scb->orig_type = iter->type; - scb->position = 0; - - entrycomp = ignore_case ? git_index_entry__cmp_icase : git_index_entry__cmp; - - if (git_vector_init(&scb->entries, 16, entrycomp) < 0 || - git_pool_init(&scb->entry_pool, sizeof(git_index_entry), 0) < 0 || - git_pool_init(&scb->string_pool, 1, 0) < 0 || - git_iterator_current(iter, &item) < 0) - goto fail; - - while (item) { - git_index_entry *clone = git_pool_malloc(&scb->entry_pool, 1); - if (!clone) - goto fail; - - memcpy(clone, item, sizeof(git_index_entry)); - - if (item->path) { - clone->path = git_pool_strdup(&scb->string_pool, item->path); - if (!clone->path) - goto fail; - } - - if (git_vector_insert(&scb->entries, clone) < 0) - goto fail; - - if (git_iterator_advance(iter, &item) < 0) - goto fail; - } - - git_vector_sort(&scb->entries); - - iter->cb = (git_iterator_callbacks *)scb; - iter->type = GIT_ITERATOR_TYPE_SPOOLANDSORT; - iter->flags ^= GIT_ITERATOR_IGNORE_CASE; - - return 0; - -fail: - spoolandsort_iterator__free_callbacks(scb); - return -1; -} - - void git_iterator_free(git_iterator *iter) { if (iter == NULL) @@ -1105,110 +1232,93 @@ void git_iterator_free(git_iterator *iter) git__free(iter); } -git_index *git_iterator_index_get_index(git_iterator *iter) +int git_iterator_set_ignore_case(git_iterator *iter, bool ignore_case) { - if (iter->type == GIT_ITERATOR_TYPE_INDEX) - return ((index_iterator *)iter)->index; + bool desire_ignore_case = (ignore_case != 0); - if (iter->type == GIT_ITERATOR_TYPE_SPOOLANDSORT && - ((spoolandsort_callbacks *)iter->cb)->orig_type == GIT_ITERATOR_TYPE_INDEX) - return ((index_iterator *)iter)->index; + if (iterator__ignore_case(iter) == desire_ignore_case) + return 0; - return NULL; + if (iter->type == GIT_ITERATOR_TYPE_EMPTY) { + if (desire_ignore_case) + iter->flags |= GIT_ITERATOR_IGNORE_CASE; + else + iter->flags &= ~GIT_ITERATOR_IGNORE_CASE; + } else { + giterr_set(GITERR_INVALID, + "Cannot currently set ignore case on non-empty iterators"); + return -1; + } + + return 0; } -git_iterator_type_t git_iterator_inner_type(git_iterator *iter) +git_index *git_iterator_get_index(git_iterator *iter) { - if (iter->type == GIT_ITERATOR_TYPE_SPOOLANDSORT) - return ((spoolandsort_callbacks *)iter->cb)->orig_type; - - return iter->type; + if (iter->type == GIT_ITERATOR_TYPE_INDEX) + return ((index_iterator *)iter)->index; + return NULL; } int git_iterator_current_tree_entry( - git_iterator *iter, const git_tree_entry **tree_entry) + const git_tree_entry **tree_entry, git_iterator *iter) { - *tree_entry = (iter->type != GIT_ITERATOR_TYPE_TREE) ? NULL : - tree_iterator__tree_entry((tree_iterator *)iter); + if (iter->type != GIT_ITERATOR_TYPE_TREE) + *tree_entry = NULL; + else { + tree_iterator_frame *tf = ((tree_iterator *)iter)->head; + *tree_entry = (tf->current < tf->n_entries) ? + tf->entries[tf->current]->te : NULL; + } + return 0; } int git_iterator_current_parent_tree( + const git_tree **tree_ptr, git_iterator *iter, - const char *parent_path, - const git_tree **tree_ptr) + const char *parent_path) { tree_iterator *ti = (tree_iterator *)iter; tree_iterator_frame *tf; const char *scan = parent_path; - int (*strncomp)(const char *a, const char *b, size_t sz); - - if (iter->type != GIT_ITERATOR_TYPE_TREE || ti->stack == NULL) - goto notfound; + const git_tree_entry *te; - strncomp = ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0) ? - git__strncasecmp : git__strncmp; + *tree_ptr = NULL; - for (tf = ti->tail; tf != NULL; tf = tf->prev) { - const git_tree_entry *te; + if (iter->type != GIT_ITERATOR_TYPE_TREE) + return 0; - if (!*scan) { - *tree_ptr = tf->tree; + for (tf = ti->root; *scan; ) { + if (!(tf = tf->down) || + tf->current >= tf->n_entries || + !(te = tf->entries[tf->current]->te) || + ti->strncomp(scan, te->filename, te->filename_len) != 0) return 0; - } - - te = git_tree_entry_byindex(tf->tree, - tf->icase_map ? (size_t)tf->icase_map[tf->index] : tf->index); - - if (strncomp(scan, te->filename, te->filename_len) != 0) - goto notfound; scan += te->filename_len; - - if (*scan) { - if (*scan != '/') - goto notfound; + if (*scan == '/') scan++; - } } -notfound: - *tree_ptr = NULL; + *tree_ptr = tf->entries[tf->current]->tree; return 0; } -int git_iterator_current_is_ignored(git_iterator *iter) +bool git_iterator_current_is_ignored(git_iterator *iter) { workdir_iterator *wi = (workdir_iterator *)iter; if (iter->type != GIT_ITERATOR_TYPE_WORKDIR) - return 0; + return false; if (wi->is_ignored != -1) - return wi->is_ignored; + return (bool)(wi->is_ignored != 0); if (git_ignore__lookup(&wi->ignores, wi->entry.path, &wi->is_ignored) < 0) - wi->is_ignored = 1; + wi->is_ignored = true; - return wi->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_TYPE_WORKDIR && - wi->entry.path && - (wi->entry.mode == GIT_FILEMODE_TREE || - wi->entry.mode == GIT_FILEMODE_COMMIT)) - { - 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; + return (bool)wi->is_ignored; } int git_iterator_cmp(git_iterator *iter, const char *path_prefix) @@ -1216,8 +1326,7 @@ 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) + if (git_iterator_current(&entry, iter) < 0 || entry == NULL) return 1; /* a NULL prefix is after any valid iterator */ @@ -1227,7 +1336,7 @@ int git_iterator_cmp(git_iterator *iter, const char *path_prefix) return iter->prefixcomp(entry->path, path_prefix); } -int git_iterator_current_workdir_path(git_iterator *iter, git_buf **path) +int git_iterator_current_workdir_path(git_buf **path, git_iterator *iter) { workdir_iterator *wi = (workdir_iterator *)iter; @@ -1238,4 +1347,3 @@ int git_iterator_current_workdir_path(git_iterator *iter, git_buf **path) return 0; } - diff --git a/src/iterator.h b/src/iterator.h index a9bccfca8..4a4e6a9d8 100644 --- a/src/iterator.h +++ b/src/iterator.h @@ -12,11 +12,6 @@ #include "vector.h" #include "buffer.h" -#define ITERATOR_PREFIXCMP(ITER, STR, PREFIX) \ - (((ITER).flags & GIT_ITERATOR_IGNORE_CASE) != 0 ? \ - git__prefixcmp_icase((STR), (PREFIX)) : \ - git__prefixcmp((STR), (PREFIX))) - typedef struct git_iterator git_iterator; typedef enum { @@ -24,20 +19,26 @@ typedef enum { GIT_ITERATOR_TYPE_TREE = 1, GIT_ITERATOR_TYPE_INDEX = 2, GIT_ITERATOR_TYPE_WORKDIR = 3, - GIT_ITERATOR_TYPE_SPOOLANDSORT = 4 } git_iterator_type_t; typedef enum { - GIT_ITERATOR_IGNORE_CASE = (1 << 0), /* ignore_case */ - GIT_ITERATOR_DONT_IGNORE_CASE = (1 << 1), /* force ignore_case off */ + /** ignore case for entry sort order */ + GIT_ITERATOR_IGNORE_CASE = (1 << 0), + /** force case sensitivity for entry sort order */ + GIT_ITERATOR_DONT_IGNORE_CASE = (1 << 1), + /** return tree items in addition to blob items */ + GIT_ITERATOR_INCLUDE_TREES = (1 << 2), + /** don't flatten trees, requiring advance_into (implies INCLUDE_TREES) */ + GIT_ITERATOR_DONT_AUTOEXPAND = (1 << 3), } git_iterator_flag_t; typedef struct { - int (*current)(git_iterator *, const git_index_entry **); - int (*at_end)(git_iterator *); - int (*advance)(git_iterator *, const git_index_entry **); + int (*current)(const git_index_entry **, git_iterator *); + int (*advance)(const git_index_entry **, git_iterator *); + int (*advance_into)(const git_index_entry **, git_iterator *); int (*seek)(git_iterator *, const char *prefix); int (*reset)(git_iterator *, const char *start, const char *end); + int (*at_end)(git_iterator *); void (*free)(git_iterator *); } git_iterator_callbacks; @@ -52,86 +53,92 @@ struct git_iterator { }; extern int git_iterator_for_nothing( - git_iterator **out, git_iterator_flag_t flags); + git_iterator **out, + git_iterator_flag_t flags, + const char *start, + const char *end); /* tree iterators will match the ignore_case value from the index of the * repository, unless you override with a non-zero flag value */ -extern int git_iterator_for_tree_range( +extern int git_iterator_for_tree( git_iterator **out, git_tree *tree, git_iterator_flag_t flags, const char *start, const char *end); -GIT_INLINE(int) git_iterator_for_tree(git_iterator **out, git_tree *tree) -{ - return git_iterator_for_tree_range(out, tree, 0, NULL, NULL); -} - /* index iterators will take the ignore_case value from the index; the * ignore_case flags are not used */ -extern int git_iterator_for_index_range( +extern int git_iterator_for_index( git_iterator **out, git_index *index, git_iterator_flag_t flags, const char *start, const char *end); -GIT_INLINE(int) git_iterator_for_index(git_iterator **out, git_index *index) -{ - return git_iterator_for_index_range(out, index, 0, NULL, NULL); -} - /* workdir iterators will match the ignore_case value from the index of the * repository, unless you override with a non-zero flag value */ -extern int git_iterator_for_workdir_range( +extern int git_iterator_for_workdir( git_iterator **out, git_repository *repo, git_iterator_flag_t flags, const char *start, const char *end); -GIT_INLINE(int) git_iterator_for_workdir(git_iterator **out, git_repository *repo) -{ - return git_iterator_for_workdir_range(out, repo, 0, NULL, NULL); -} - extern void git_iterator_free(git_iterator *iter); -/* Spool all iterator values, resort with alternative ignore_case value - * and replace callbacks with spoolandsort alternates. - */ -extern int git_iterator_spoolandsort_push(git_iterator *iter, bool ignore_case); - -/* Restore original callbacks - not required in most circumstances */ -extern void git_iterator_spoolandsort_pop(git_iterator *iter); - -/* 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. +/* Return a git_index_entry structure for the current value the iterator + * is looking at or NULL if the iterator is at the end. + * + * The entry may noy be fully populated. Tree iterators will only have a + * value mode, OID, and path. Workdir iterators will not have an OID (but + * you can use `git_iterator_current_oid()` to calculate it on demand). * * 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. + * Once you call `git_iterator_advance()` then the old entry is no longer + * guaranteed to be valid - it may be freed or just overwritten in place. */ GIT_INLINE(int) git_iterator_current( - git_iterator *iter, const git_index_entry **entry) + const git_index_entry **entry, git_iterator *iter) { - return iter->cb->current(iter, entry); + return iter->cb->current(entry, iter); } -GIT_INLINE(int) git_iterator_at_end(git_iterator *iter) +/** + * Advance to the next item for the iterator. + * + * If GIT_ITERATOR_INCLUDE_TREES is set, this may be a tree item. If + * GIT_ITERATOR_DONT_AUTOEXPAND is set, calling this again when on a tree + * item will skip over all the items under that tree. + */ +GIT_INLINE(int) git_iterator_advance( + const git_index_entry **entry, git_iterator *iter) { - return iter->cb->at_end(iter); + return iter->cb->advance(entry, iter); } -GIT_INLINE(int) git_iterator_advance( - git_iterator *iter, const git_index_entry **entry) +/** + * Iterate into a tree item (when GIT_ITERATOR_DONT_AUTOEXPAND is set). + * + * git_iterator_advance() steps through all items being iterated over + * (either with or without trees, depending on GIT_ITERATOR_INCLUDE_TREES), + * but if GIT_ITERATOR_DONT_AUTOEXPAND is set, it will skip to the next + * sibling of a tree instead of going to the first child of the tree. In + * that case, use this function to advance to the first child of the tree. + * + * If the current item is not a tree, this is a no-op. + * + * For working directory iterators only, a tree (i.e. directory) can be + * empty. In that case, this function returns GIT_ENOTFOUND and does not + * advance. That can't happen for tree and index iterators. + */ +GIT_INLINE(int) git_iterator_advance_into( + const git_index_entry **entry, git_iterator *iter) { - return iter->cb->advance(iter, entry); + return iter->cb->advance_into(entry, iter); } GIT_INLINE(int) git_iterator_seek( @@ -146,6 +153,11 @@ GIT_INLINE(int) git_iterator_reset( return iter->cb->reset(iter, start, end); } +GIT_INLINE(int) git_iterator_at_end(git_iterator *iter) +{ + return iter->cb->at_end(iter); +} + GIT_INLINE(git_iterator_type_t) git_iterator_type(git_iterator *iter) { return iter->type; @@ -166,47 +178,28 @@ GIT_INLINE(bool) git_iterator_ignore_case(git_iterator *iter) return ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0); } +extern int git_iterator_set_ignore_case(git_iterator *iter, bool ignore_case); + extern int git_iterator_current_tree_entry( - git_iterator *iter, const git_tree_entry **tree_entry); + const git_tree_entry **entry_out, git_iterator *iter); extern int git_iterator_current_parent_tree( - git_iterator *iter, const char *parent_path, const git_tree **tree_ptr); + const git_tree **tree_out, git_iterator *iter, const char *parent_path); -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 bool git_iterator_current_is_ignored(git_iterator *iter); extern int git_iterator_cmp( git_iterator *iter, const char *path_prefix); /** - * Get the full path of the current item from a workdir iterator. - * This will return NULL for a non-workdir iterator. + * Get full path of the current item from a workdir iterator. This will + * return NULL for a non-workdir iterator. The git_buf is still owned by + * the iterator; this is exposed just for efficiency. */ extern int git_iterator_current_workdir_path( - git_iterator *iter, git_buf **path); - - -extern git_index *git_iterator_index_get_index(git_iterator *iter); + git_buf **path, git_iterator *iter); -extern git_iterator_type_t git_iterator_inner_type(git_iterator *iter); +/* Return index pointer if index iterator, else NULL */ +extern git_index *git_iterator_get_index(git_iterator *iter); #endif diff --git a/src/mwindow.c b/src/mwindow.c index cb2ef78b0..b35503d46 100644 --- a/src/mwindow.c +++ b/src/mwindow.c @@ -33,7 +33,7 @@ static git_mwindow_ctl mem_ctl; void git_mwindow_free_all(git_mwindow_file *mwf) { git_mwindow_ctl *ctl = &mem_ctl; - unsigned int i; + size_t i; if (git_mutex_lock(&git__mwindow_mutex)) { giterr_set(GITERR_THREAD, "unable to lock mwindow mutex"); @@ -115,7 +115,7 @@ static void git_mwindow_scan_lru( static int git_mwindow_close_lru(git_mwindow_file *mwf) { git_mwindow_ctl *ctl = &mem_ctl; - unsigned int i; + size_t i; git_mwindow *lru_w = NULL, *lru_l = NULL, **list = &mwf->windows; /* FIXME: Does this give us any advantage? */ @@ -288,7 +288,7 @@ void git_mwindow_file_deregister(git_mwindow_file *mwf) { git_mwindow_ctl *ctl = &mem_ctl; git_mwindow_file *cur; - unsigned int i; + size_t i; if (git_mutex_lock(&git__mwindow_mutex)) return; diff --git a/src/notes.c b/src/notes.c index f5537db3f..ef48ac88e 100644 --- a/src/notes.c +++ b/src/notes.c @@ -531,14 +531,11 @@ void git_note_free(git_note *note) static int process_entry_path( const char* entry_path, - const git_oid *note_oid, - git_note_foreach_cb note_cb, - void *payload) + git_oid *annotated_object_id) { int error = -1; size_t i = 0, j = 0, len; git_buf buf = GIT_BUF_INIT; - git_oid annotated_object_id; if ((error = git_buf_puts(&buf, entry_path)) < 0) goto cleanup; @@ -571,11 +568,7 @@ static int process_entry_path( goto cleanup; } - if ((error = git_oid_fromstr(&annotated_object_id, buf.ptr)) < 0) - goto cleanup; - - if (note_cb(note_oid, &annotated_object_id, payload)) - error = GIT_EUSER; + error = git_oid_fromstr(annotated_object_id, buf.ptr); cleanup: git_buf_free(&buf); @@ -583,32 +576,86 @@ cleanup: } int git_note_foreach( + git_repository *repo, + const char *notes_ref, + git_note_foreach_cb note_cb, + void *payload) +{ + int error; + git_note_iterator *iter = NULL; + git_oid note_id, annotated_id; + + if ((error = git_note_iterator_new(&iter, repo, notes_ref)) < 0) + return error; + + while (!(error = git_note_next(¬e_id, &annotated_id, iter))) { + if (note_cb(¬e_id, &annotated_id, payload)) { + error = GIT_EUSER; + break; + } + } + + if (error == GIT_ITEROVER) + error = 0; + + git_note_iterator_free(iter); + return error; +} + + +void git_note_iterator_free(git_note_iterator *it) +{ + if (it == NULL) + return; + + git_iterator_free(it); +} + + +int git_note_iterator_new( + git_note_iterator **it, git_repository *repo, - const char *notes_ref, - git_note_foreach_cb note_cb, - void *payload) + const char *notes_ref) { int error; - git_iterator *iter = NULL; - git_tree *tree = NULL; git_commit *commit = NULL; - const git_index_entry *item; - - if (!(error = retrieve_note_tree_and_commit( - &tree, &commit, repo, ¬es_ref)) && - !(error = git_iterator_for_tree(&iter, tree))) - error = git_iterator_current(iter, &item); + git_tree *tree = NULL; - while (!error && item) { - error = process_entry_path(item->path, &item->oid, note_cb, payload); + error = retrieve_note_tree_and_commit(&tree, &commit, repo, ¬es_ref); + if (error < 0) + goto cleanup; - if (!error) - error = git_iterator_advance(iter, &item); - } + if ((error = git_iterator_for_tree(it, tree, 0, NULL, NULL)) < 0) + git_iterator_free(*it); - git_iterator_free(iter); +cleanup: git_tree_free(tree); git_commit_free(commit); return error; } + +int git_note_next( + git_oid* note_id, + git_oid* annotated_id, + git_note_iterator *it) +{ + int error; + const git_index_entry *item; + + if ((error = git_iterator_current(&item, it)) < 0) + goto exit; + + if (item != NULL) { + git_oid_cpy(note_id, &item->oid); + error = process_entry_path(item->path, annotated_id); + + if (error >= 0) + error = git_iterator_advance(NULL, it); + } else { + error = GIT_ITEROVER; + } + +exit: + return error; +} diff --git a/src/notes.h b/src/notes.h index 2f119e3c3..39e18b621 100644 --- a/src/notes.h +++ b/src/notes.h @@ -10,6 +10,7 @@ #include "common.h" #include "git2/oid.h" +#include "git2/types.h" #define GIT_NOTES_DEFAULT_REF "refs/notes/commits" @@ -32,6 +32,8 @@ typedef struct int is_alternate; } backend_internal; +size_t git_odb__cache_size = GIT_DEFAULT_CACHE_SIZE; + static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth); int git_odb__format_object_header(char *hdr, size_t n, size_t obj_len, git_otype obj_type) @@ -351,7 +353,7 @@ int git_odb_new(git_odb **out) git_odb *db = git__calloc(1, sizeof(*db)); GITERR_CHECK_ALLOC(db); - if (git_cache_init(&db->cache, GIT_DEFAULT_CACHE_SIZE, &free_odb_object) < 0 || + if (git_cache_init(&db->cache, git_odb__cache_size, &free_odb_object) < 0 || git_vector_init(&db->backends, 4, backend_sort_cmp) < 0) { git__free(db); @@ -499,7 +501,7 @@ int git_odb_open(git_odb **out, const char *objects_dir) static void odb_free(git_odb *db) { - unsigned int i; + size_t i; for (i = 0; i < db->backends.length; ++i) { backend_internal *internal = git_vector_get(&db->backends, i); @@ -527,7 +529,7 @@ void git_odb_free(git_odb *db) int git_odb_exists(git_odb *db, const git_oid *id) { git_odb_object *object; - unsigned int i; + size_t i; bool found = false; bool refreshed = false; @@ -577,7 +579,7 @@ int git_odb__read_header_or_object( git_odb_object **out, size_t *len_p, git_otype *type_p, git_odb *db, const git_oid *id) { - unsigned int i; + size_t i; int error = GIT_ENOTFOUND; git_odb_object *object; @@ -619,7 +621,7 @@ int git_odb__read_header_or_object( int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) { - unsigned int i; + size_t i; int error; bool refreshed = false; git_rawobj raw; @@ -664,7 +666,7 @@ attempt_lookup: int git_odb_read_prefix( git_odb_object **out, git_odb *db, const git_oid *short_id, size_t len) { - unsigned int i; + size_t i; int error = GIT_ENOTFOUND; git_oid found_full_oid = {{0}}; git_rawobj raw; @@ -743,7 +745,7 @@ int git_odb_foreach(git_odb *db, git_odb_foreach_cb cb, void *payload) int git_odb_write( git_oid *oid, git_odb *db, const void *data, size_t len, git_otype type) { - unsigned int i; + size_t i; int error = GIT_ERROR; git_odb_stream *stream; @@ -785,7 +787,7 @@ int git_odb_write( int git_odb_open_wstream( git_odb_stream **stream, git_odb *db, size_t size, git_otype type) { - unsigned int i; + size_t i; int error = GIT_ERROR; assert(stream && db); @@ -812,7 +814,7 @@ int git_odb_open_wstream( int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oid) { - unsigned int i; + size_t i; int error = GIT_ERROR; assert(stream && db); @@ -833,7 +835,7 @@ int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oi int git_odb_write_pack(struct git_odb_writepack **out, git_odb *db, git_transfer_progress_callback progress_cb, void *progress_payload) { - unsigned int i; + size_t i; int error = GIT_ERROR; assert(out && db); @@ -864,7 +866,7 @@ void *git_odb_backend_malloc(git_odb_backend *backend, size_t len) int git_odb_refresh(struct git_odb *db) { - unsigned int i; + size_t i; assert(db); for (i = 0; i < db->backends.length; ++i) { diff --git a/src/odb_pack.c b/src/odb_pack.c index 9779ecd25..7240a4ac7 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -132,9 +132,6 @@ struct pack_writepack { * ***********************************************************/ -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 int packfile_load__cb(void *_data, git_buf *path); @@ -162,23 +159,6 @@ static int pack_entry_find_prefix( * ***********************************************************/ -GIT_INLINE(void) pack_window_free_all(struct pack_backend *backend, struct git_pack_file *p) -{ - GIT_UNUSED(backend); - git_mwindow_free_all(&p->mwf); -} - -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 - * the object header and delta base parsing routines below. - */ - return git_mwindow_contains(win, offset + 20); -} - static int packfile_sort__cb(const void *a_, const void *b_) { const struct git_pack_file *a = a_; @@ -215,7 +195,7 @@ static int packfile_load__cb(void *_data, git_buf *path) struct pack_backend *backend = (struct pack_backend *)_data; struct git_pack_file *pack; int error; - unsigned int i; + size_t i; if (git__suffixcmp(path->ptr, ".idx") != 0) return 0; /* not an index */ @@ -242,7 +222,7 @@ static int pack_entry_find_inner( const git_oid *oid, struct git_pack_file *last_found) { - unsigned int i; + size_t i; if (last_found && git_pack_entry_find(e, last_found, oid, GIT_OID_HEXSZ) == 0) @@ -286,7 +266,7 @@ static unsigned pack_entry_find_prefix_inner( struct git_pack_file *last_found) { int error; - unsigned int i; + size_t i; unsigned found = 0; if (last_found) { @@ -530,7 +510,7 @@ static int pack_backend__writepack(struct git_odb_writepack **out, static void pack_backend__free(git_odb_backend *_backend) { struct pack_backend *backend; - unsigned int i; + size_t i; assert(_backend); @@ -25,7 +25,7 @@ int git_oid_fromstrn(git_oid *out, const char *str, size_t length) int v; if (length > GIT_OID_HEXSZ) - length = GIT_OID_HEXSZ; + return oid_error_invalid("too long"); for (p = 0; p < length - 1; p += 2) { v = (git__fromhex(str[p + 0]) << 4) @@ -51,6 +51,11 @@ int git_oid_fromstrn(git_oid *out, const char *str, size_t length) return 0; } +int git_oid_fromstrp(git_oid *out, const char *str) +{ + return git_oid_fromstrn(out, str, strlen(str)); +} + int git_oid_fromstr(git_oid *out, const char *str) { return git_oid_fromstrn(out, str, GIT_OID_HEXSZ); diff --git a/src/path.c b/src/path.c index 263cf9e7c..6437979d5 100644 --- a/src/path.c +++ b/src/path.c @@ -679,37 +679,14 @@ int git_path_apply_relative(git_buf *target, const char *relpath) int git_path_cmp( const char *name1, size_t len1, int isdir1, - const char *name2, size_t len2, int isdir2) + const char *name2, size_t len2, int isdir2, + int (*compare)(const char *, const char *, size_t)) { unsigned char c1, c2; size_t len = len1 < len2 ? len1 : len2; int cmp; - cmp = memcmp(name1, name2, len); - if (cmp) - return cmp; - - c1 = name1[len]; - c2 = name2[len]; - - if (c1 == '\0' && isdir1) - c1 = '/'; - - if (c2 == '\0' && isdir2) - c2 = '/'; - - return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; -} - -int git_path_icmp( - const char *name1, size_t len1, int isdir1, - const char *name2, size_t len2, int isdir2) -{ - unsigned char c1, c2; - size_t len = len1 < len2 ? len1 : len2; - int cmp; - - cmp = strncasecmp(name1, name2, len); + cmp = compare(name1, name2, len); if (cmp) return cmp; @@ -900,15 +877,22 @@ int git_path_dirload_with_stat( if (cmp_len && strncomp(ps->path, end_stat, cmp_len) > 0) continue; + git_buf_truncate(&full, prefix_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[ps->path_len++] = '/'; - ps->path[ps->path_len] = '\0'; + if ((error = git_buf_joinpath(&full, full.ptr, ".git")) < 0) + break; + + if (p_access(full.ptr, F_OK) == 0) { + ps->st.st_mode = GIT_FILEMODE_COMMIT; + } else { + ps->path[ps->path_len++] = '/'; + ps->path[ps->path_len] = '\0'; + } } } diff --git a/src/path.h b/src/path.h index feefd65d1..ead4fa338 100644 --- a/src/path.h +++ b/src/path.h @@ -265,12 +265,8 @@ extern int git_path_direach( */ extern int git_path_cmp( const char *name1, size_t len1, int isdir1, - const char *name2, size_t len2, int isdir2); - -/** Path sort function that is case insensitive */ -extern int git_path_icmp( - const char *name1, size_t len1, int isdir1, - const char *name2, size_t len2, int isdir2); + const char *name2, size_t len2, int isdir2, + int (*compare)(const char *, const char *, size_t)); /** * Invoke callback up path directory by directory until the ceiling is diff --git a/src/pool.c b/src/pool.c index 64b5c6b00..b3cd49665 100644 --- a/src/pool.c +++ b/src/pool.c @@ -10,6 +10,10 @@ struct git_pool_page { char data[GIT_FLEX_ARRAY]; }; +struct pool_freelist { + struct pool_freelist *next; +}; + #define GIT_POOL_MIN_USABLE 4 #define GIT_POOL_MIN_PAGESZ 2 * sizeof(void*) @@ -150,7 +154,7 @@ void *git_pool_malloc(git_pool *pool, uint32_t items) pool->has_multi_item_alloc = 1; else if (pool->free_list != NULL) { ptr = pool->free_list; - pool->free_list = *((void **)pool->free_list); + pool->free_list = ((struct pool_freelist *)pool->free_list)->next; return ptr; } @@ -235,10 +239,31 @@ char *git_pool_strcat(git_pool *pool, const char *a, const char *b) void git_pool_free(git_pool *pool, void *ptr) { - assert(pool && ptr && pool->item_size >= sizeof(void*)); + struct pool_freelist *item = ptr; + + assert(pool && pool->item_size >= sizeof(void*)); + + if (item) { + item->next = pool->free_list; + pool->free_list = item; + } +} + +void git_pool_free_array(git_pool *pool, size_t count, void **ptrs) +{ + struct pool_freelist **items = (struct pool_freelist **)ptrs; + size_t i; + + assert(pool && ptrs && pool->item_size >= sizeof(void*)); + + if (!count) + return; + + for (i = count - 1; i > 0; --i) + items[i]->next = items[i - 1]; - *((void **)ptr) = pool->free_list; - pool->free_list = ptr; + items[i]->next = pool->free_list; + pool->free_list = items[count - 1]; } uint32_t git_pool__open_pages(git_pool *pool) diff --git a/src/pool.h b/src/pool.h index 2b262a588..5ac9b764f 100644 --- a/src/pool.h +++ b/src/pool.h @@ -126,6 +126,13 @@ extern char *git_pool_strcat(git_pool *pool, const char *a, const char *b); */ extern void git_pool_free(git_pool *pool, void *ptr); +/** + * Push an array of pool allocated blocks efficiently onto the free list. + * + * This has the same constraints as `git_pool_free()` above. + */ +extern void git_pool_free_array(git_pool *pool, size_t count, void **ptrs); + /* * Misc utilities */ diff --git a/src/posix.h b/src/posix.h index 9dd0c94d3..719c8a04c 100644 --- a/src/posix.h +++ b/src/posix.h @@ -32,14 +32,13 @@ typedef int git_file; * Standard POSIX Methods * * All the methods starting with the `p_` prefix are - * direct ports of the standard POSIX methods. + * 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); diff --git a/src/push.c b/src/push.c index 628df7ac4..37f641812 100644 --- a/src/push.c +++ b/src/push.c @@ -83,18 +83,6 @@ static void free_refspec(push_spec *spec) git__free(spec); } -static void free_status(push_status *status) -{ - if (status == NULL) - return; - - if (status->msg) - git__free(status->msg); - - git__free(status->ref); - git__free(status); -} - static int check_rref(char *ref) { if (git__prefixcmp(ref, "refs/")) { @@ -225,8 +213,11 @@ int git_push_update_tips(git_push *push) error = git_reference_lookup(&remote_ref, push->remote->repo, git_buf_cstr(&remote_ref_name)); if (!error) { - if ((error = git_reference_delete(remote_ref)) < 0) + if ((error = git_reference_delete(remote_ref)) < 0) { + git_reference_free(remote_ref); goto on_error; + } + git_reference_free(remote_ref); } else if (error == GIT_ENOTFOUND) giterr_clear(); else @@ -526,6 +517,18 @@ int git_push_status_foreach(git_push *push, return 0; } +void git_push_status_free(push_status *status) +{ + if (status == NULL) + return; + + if (status->msg) + git__free(status->msg); + + git__free(status->ref); + git__free(status); +} + void git_push_free(git_push *push) { push_spec *spec; @@ -541,7 +544,7 @@ void git_push_free(git_push *push) git_vector_free(&push->specs); git_vector_foreach(&push->status, i, status) { - free_status(status); + git_push_status_free(status); } git_vector_free(&push->status); diff --git a/src/push.h b/src/push.h index 629583189..e982b8385 100644 --- a/src/push.h +++ b/src/push.h @@ -41,4 +41,11 @@ struct git_push { unsigned pb_parallelism; }; +/** + * Free the given push status object + * + * @param status The push status object + */ +void git_push_status_free(push_status *status); + #endif diff --git a/src/refdb.c b/src/refdb.c new file mode 100644 index 000000000..0d2064343 --- /dev/null +++ b/src/refdb.c @@ -0,0 +1,177 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * 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 "git2/object.h" +#include "git2/refs.h" +#include "git2/refdb.h" +#include "hash.h" +#include "refdb.h" +#include "refs.h" + +#include "git2/refdb_backend.h" + +int git_refdb_new(git_refdb **out, git_repository *repo) +{ + git_refdb *db; + + assert(out && repo); + + db = git__calloc(1, sizeof(*db)); + GITERR_CHECK_ALLOC(db); + + db->repo = repo; + + *out = db; + GIT_REFCOUNT_INC(db); + return 0; +} + +int git_refdb_open(git_refdb **out, git_repository *repo) +{ + git_refdb *db; + git_refdb_backend *dir; + + assert(out && repo); + + *out = NULL; + + if (git_refdb_new(&db, repo) < 0) + return -1; + + /* Add the default (filesystem) backend */ + if (git_refdb_backend_fs(&dir, repo, db) < 0) { + git_refdb_free(db); + return -1; + } + + db->repo = repo; + db->backend = dir; + + *out = db; + return 0; +} + +int git_refdb_set_backend(git_refdb *db, git_refdb_backend *backend) +{ + if (db->backend) { + if(db->backend->free) + db->backend->free(db->backend); + else + git__free(db->backend); + } + + db->backend = backend; + + return 0; +} + +int git_refdb_compress(git_refdb *db) +{ + assert(db); + + if (db->backend->compress) { + return db->backend->compress(db->backend); + } + + return 0; +} + +void git_refdb_free(git_refdb *db) +{ + if (db->backend) { + if(db->backend->free) + db->backend->free(db->backend); + else + git__free(db->backend); + } + + git__free(db); +} + +int git_refdb_exists(int *exists, git_refdb *refdb, const char *ref_name) +{ + assert(exists && refdb && refdb->backend); + + return refdb->backend->exists(exists, refdb->backend, ref_name); +} + +int git_refdb_lookup(git_reference **out, git_refdb *db, const char *ref_name) +{ + assert(db && db->backend && ref_name); + + return db->backend->lookup(out, db->backend, ref_name); +} + +int git_refdb_foreach( + git_refdb *db, + unsigned int list_flags, + git_reference_foreach_cb callback, + void *payload) +{ + assert(db && db->backend); + + return db->backend->foreach(db->backend, list_flags, callback, payload); +} + +struct glob_cb_data { + const char *glob; + git_reference_foreach_cb callback; + void *payload; +}; + +static int fromglob_cb(const char *reference_name, void *payload) +{ + struct glob_cb_data *data = (struct glob_cb_data *)payload; + + if (!p_fnmatch(data->glob, reference_name, 0)) + return data->callback(reference_name, data->payload); + + return 0; +} + +int git_refdb_foreach_glob( + git_refdb *db, + const char *glob, + unsigned int list_flags, + git_reference_foreach_cb callback, + void *payload) +{ + int error; + struct glob_cb_data data; + + assert(db && db->backend && glob && callback); + + if(db->backend->foreach_glob != NULL) + error = db->backend->foreach_glob(db->backend, + glob, list_flags, callback, payload); + else { + data.glob = glob; + data.callback = callback; + data.payload = payload; + + error = db->backend->foreach(db->backend, + list_flags, fromglob_cb, &data); + } + + return error; +} + +int git_refdb_write(git_refdb *db, const git_reference *ref) +{ + assert(db && db->backend); + + return db->backend->write(db->backend, ref); +} + +int git_refdb_delete(struct git_refdb *db, const git_reference *ref) +{ + assert(db && db->backend); + + return db->backend->delete(db->backend, ref); +} diff --git a/src/refdb.h b/src/refdb.h new file mode 100644 index 000000000..0969711b9 --- /dev/null +++ b/src/refdb.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * 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_refdb_h__ +#define INCLUDE_refdb_h__ + +#include "git2/refdb.h" +#include "repository.h" + +struct git_refdb { + git_refcount rc; + git_repository *repo; + git_refdb_backend *backend; +}; + +int git_refdb_exists( + int *exists, + git_refdb *refdb, + const char *ref_name); + +int git_refdb_lookup( + git_reference **out, + git_refdb *refdb, + const char *ref_name); + +int git_refdb_foreach( + git_refdb *refdb, + unsigned int list_flags, + git_reference_foreach_cb callback, + void *payload); + +int git_refdb_foreach_glob( + git_refdb *refdb, + const char *glob, + unsigned int list_flags, + git_reference_foreach_cb callback, + void *payload); + +int git_refdb_write(git_refdb *refdb, const git_reference *ref); + +int git_refdb_delete(struct git_refdb *refdb, const git_reference *ref); + +#endif diff --git a/src/refdb_fs.c b/src/refdb_fs.c new file mode 100644 index 000000000..f00bd72a0 --- /dev/null +++ b/src/refdb_fs.c @@ -0,0 +1,1023 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * 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 "config.h" +#include "refdb.h" +#include "refdb_fs.h" + +#include <git2/tag.h> +#include <git2/object.h> +#include <git2/refdb.h> +#include <git2/refdb_backend.h> + +GIT__USE_STRMAP; + +#define DEFAULT_NESTING_LEVEL 5 +#define MAX_NESTING_LEVEL 10 + +enum { + GIT_PACKREF_HAS_PEEL = 1, + GIT_PACKREF_WAS_LOOSE = 2 +}; + +struct packref { + git_oid oid; + git_oid peel; + char flags; + char name[GIT_FLEX_ARRAY]; +}; + +typedef struct refdb_fs_backend { + git_refdb_backend parent; + + git_repository *repo; + const char *path; + git_refdb *refdb; + + git_refcache refcache; +} refdb_fs_backend; + +static int reference_read( + git_buf *file_content, + time_t *mtime, + const char *repo_path, + const char *ref_name, + int *updated) +{ + git_buf path = GIT_BUF_INIT; + int result; + + assert(file_content && repo_path && ref_name); + + /* Determine the full path of the file */ + if (git_buf_joinpath(&path, repo_path, ref_name) < 0) + return -1; + + result = git_futils_readbuffer_updated(file_content, path.ptr, mtime, NULL, updated); + git_buf_free(&path); + + return result; +} + +static int packed_parse_oid( + struct packref **ref_out, + const char **buffer_out, + const char *buffer_end) +{ + struct packref *ref = NULL; + + const char *buffer = *buffer_out; + const char *refname_begin, *refname_end; + + size_t refname_len; + git_oid id; + + refname_begin = (buffer + GIT_OID_HEXSZ + 1); + if (refname_begin >= buffer_end || refname_begin[-1] != ' ') + goto corrupt; + + /* Is this a valid object id? */ + if (git_oid_fromstr(&id, buffer) < 0) + goto corrupt; + + refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin); + if (refname_end == NULL) + refname_end = buffer_end; + + if (refname_end[-1] == '\r') + refname_end--; + + refname_len = refname_end - refname_begin; + + ref = git__malloc(sizeof(struct packref) + refname_len + 1); + GITERR_CHECK_ALLOC(ref); + + memcpy(ref->name, refname_begin, refname_len); + ref->name[refname_len] = 0; + + git_oid_cpy(&ref->oid, &id); + + ref->flags = 0; + + *ref_out = ref; + *buffer_out = refname_end + 1; + + return 0; + +corrupt: + git__free(ref); + giterr_set(GITERR_REFERENCE, "The packed references file is corrupted"); + return -1; +} + +static int packed_parse_peel( + struct packref *tag_ref, + const char **buffer_out, + const char *buffer_end) +{ + const char *buffer = *buffer_out + 1; + + assert(buffer[-1] == '^'); + + /* Ensure it's not the first entry of the file */ + if (tag_ref == NULL) + goto corrupt; + + /* Ensure reference is a tag */ + if (git__prefixcmp(tag_ref->name, GIT_REFS_TAGS_DIR) != 0) + goto corrupt; + + if (buffer + GIT_OID_HEXSZ > buffer_end) + goto corrupt; + + /* Is this 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 != buffer_end) { + if (*buffer == '\n') + buffer++; + else + goto corrupt; + } + + *buffer_out = buffer; + return 0; + +corrupt: + giterr_set(GITERR_REFERENCE, "The packed references file is corrupted"); + return -1; +} + +static int packed_load(refdb_fs_backend *backend) +{ + int result, updated; + git_buf packfile = GIT_BUF_INIT; + const char *buffer_start, *buffer_end; + git_refcache *ref_cache = &backend->refcache; + + /* 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); + } + + result = reference_read(&packfile, &ref_cache->packfile_time, + backend->path, GIT_PACKEDREFS_FILE, &updated); + + /* + * 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; + } + + if (result < 0) + return -1; + + if (!updated) + return 0; + + /* + * At this point, we want to refresh the packed refs. We already + * have the contents in our buffer. + */ + git_strmap_clear(ref_cache->packfile); + + buffer_start = (const char *)packfile.ptr; + buffer_end = (const char *)(buffer_start) + packfile.size; + + while (buffer_start < buffer_end && buffer_start[0] == '#') { + buffer_start = strchr(buffer_start, '\n'); + if (buffer_start == NULL) + goto parse_failed; + + buffer_start++; + } + + while (buffer_start < buffer_end) { + int err; + struct packref *ref = NULL; + + if (packed_parse_oid(&ref, &buffer_start, buffer_end) < 0) + goto parse_failed; + + if (buffer_start[0] == '^') { + if (packed_parse_peel(ref, &buffer_start, buffer_end) < 0) + goto parse_failed; + } + + git_strmap_insert(ref_cache->packfile, ref->name, ref, err); + if (err < 0) + goto parse_failed; + } + + git_buf_free(&packfile); + return 0; + +parse_failed: + git_strmap_free(ref_cache->packfile); + ref_cache->packfile = NULL; + git_buf_free(&packfile); + return -1; +} + +static int loose_parse_oid(git_oid *oid, git_buf *file_content) +{ + size_t len; + const char *str; + + len = git_buf_len(file_content); + if (len < GIT_OID_HEXSZ) + goto corrupted; + + /* str is guranteed to be zero-terminated */ + str = git_buf_cstr(file_content); + + /* we need to get 40 OID characters from the file */ + if (git_oid_fromstr(oid, git_buf_cstr(file_content)) < 0) + goto corrupted; + + /* If the file is longer than 40 chars, the 41st must be a space */ + str += GIT_OID_HEXSZ; + if (*str == '\0' || git__isspace(*str)) + return 0; + +corrupted: + giterr_set(GITERR_REFERENCE, "Corrupted loose reference file"); + return -1; +} + +static int loose_lookup_to_packfile( + struct packref **ref_out, + refdb_fs_backend *backend, + 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, backend->path, name, NULL) < 0) + return -1; + + git_buf_rtrim(&ref_file); + + 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; + } + + ref->flags = GIT_PACKREF_WAS_LOOSE; + + *ref_out = ref; + git_buf_free(&ref_file); + return 0; +} + + +static int _dirent_loose_load(void *data, git_buf *full_path) +{ + refdb_fs_backend *backend = (refdb_fs_backend *)data; + void *old_ref = NULL; + struct packref *ref; + const char *file_path; + int err; + + if (git_path_isdir(full_path->ptr) == true) + return git_path_direach(full_path, _dirent_loose_load, backend); + + file_path = full_path->ptr + strlen(backend->path); + + if (loose_lookup_to_packfile(&ref, backend, file_path) < 0) + return -1; + + git_strmap_insert2( + backend->refcache.packfile, ref->name, ref, old_ref, err); + if (err < 0) { + git__free(ref); + return -1; + } + + git__free(old_ref); + return 0; +} + +/* + * Load all the loose references from the repository + * into the in-memory Packfile, and build a vector with + * all the references so it can be written back to + * disk. + */ +static int packed_loadloose(refdb_fs_backend *backend) +{ + git_buf refs_path = GIT_BUF_INIT; + int result; + + /* the packfile must have been previously loaded! */ + assert(backend->refcache.packfile); + + if (git_buf_joinpath(&refs_path, backend->path, 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 + */ + result = git_path_direach(&refs_path, _dirent_loose_load, backend); + git_buf_free(&refs_path); + + return result; +} + +static int refdb_fs_backend__exists( + int *exists, + git_refdb_backend *_backend, + const char *ref_name) +{ + refdb_fs_backend *backend; + git_buf ref_path = GIT_BUF_INIT; + + assert(_backend); + backend = (refdb_fs_backend *)_backend; + + if (packed_load(backend) < 0) + return -1; + + if (git_buf_joinpath(&ref_path, backend->path, ref_name) < 0) + return -1; + + if (git_path_isfile(ref_path.ptr) == true || + git_strmap_exists(backend->refcache.packfile, ref_path.ptr)) + *exists = 1; + else + *exists = 0; + + git_buf_free(&ref_path); + return 0; +} + +static const char *loose_parse_symbolic(git_buf *file_content) +{ + const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF); + const char *refname_start; + + refname_start = (const char *)file_content->ptr; + + if (git_buf_len(file_content) < header_len + 1) { + giterr_set(GITERR_REFERENCE, "Corrupted loose reference file"); + return NULL; + } + + /* + * Assume we have already checked for the header + * before calling this function + */ + refname_start += header_len; + + return refname_start; +} + +static int loose_lookup( + git_reference **out, + refdb_fs_backend *backend, + const char *ref_name) +{ + const char *target; + git_oid oid; + git_buf ref_file = GIT_BUF_INIT; + int error = 0; + + error = reference_read(&ref_file, NULL, backend->path, ref_name, NULL); + + if (error < 0) + goto done; + + if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) { + git_buf_rtrim(&ref_file); + + if ((target = loose_parse_symbolic(&ref_file)) == NULL) { + error = -1; + goto done; + } + + *out = git_reference__alloc(backend->refdb, ref_name, NULL, target); + } else { + if ((error = loose_parse_oid(&oid, &ref_file)) < 0) + goto done; + + *out = git_reference__alloc(backend->refdb, ref_name, &oid, NULL); + } + + if (*out == NULL) + error = -1; + +done: + git_buf_free(&ref_file); + return error; +} + +static int packed_map_entry( + struct packref **entry, + khiter_t *pos, + refdb_fs_backend *backend, + const char *ref_name) +{ + git_strmap *packfile_refs; + + if (packed_load(backend) < 0) + return -1; + + /* Look up on the packfile */ + packfile_refs = backend->refcache.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; + } + + *entry = git_strmap_value_at(packfile_refs, *pos); + + return 0; +} + +static int packed_lookup( + git_reference **out, + refdb_fs_backend *backend, + const char *ref_name) +{ + struct packref *entry; + khiter_t pos; + int error = 0; + + if ((error = packed_map_entry(&entry, &pos, backend, ref_name)) < 0) + return error; + + if ((*out = git_reference__alloc(backend->refdb, ref_name, &entry->oid, NULL)) == NULL) + return -1; + + return 0; +} + +static int refdb_fs_backend__lookup( + git_reference **out, + git_refdb_backend *_backend, + const char *ref_name) +{ + refdb_fs_backend *backend; + int result; + + assert(_backend); + + backend = (refdb_fs_backend *)_backend; + + if ((result = loose_lookup(out, backend, ref_name)) == 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(out, backend, ref_name); + } + + return result; +} + +struct dirent_list_data { + refdb_fs_backend *backend; + size_t repo_path_len; + unsigned int list_type:2; + + git_reference_foreach_cb callback; + void *callback_payload; + int callback_error; +}; + +static git_ref_t loose_guess_rtype(const git_buf *full_path) +{ + git_buf ref_file = GIT_BUF_INIT; + git_ref_t type; + + type = GIT_REF_INVALID; + + 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; + } + + git_buf_free(&ref_file); + return type; +} + +static int _dirent_loose_listall(void *_data, git_buf *full_path) +{ + struct dirent_list_data *data = (struct dirent_list_data *)_data; + const char *file_path = full_path->ptr + data->repo_path_len; + + if (git_path_isdir(full_path->ptr) == true) + return git_path_direach(full_path, _dirent_loose_listall, _data); + + /* do not add twice a reference that exists already in the packfile */ + if (git_strmap_exists(data->backend->refcache.packfile, file_path)) + return 0; + + if (data->list_type != GIT_REF_LISTALL) { + if ((data->list_type & loose_guess_rtype(full_path)) == 0) + return 0; /* we are filtering out this reference */ + } + + /* Locked references aren't returned */ + if (!git__suffixcmp(file_path, GIT_FILELOCK_EXTENSION)) + return 0; + + if (data->callback(file_path, data->callback_payload)) + data->callback_error = GIT_EUSER; + + return data->callback_error; +} + +static int refdb_fs_backend__foreach( + git_refdb_backend *_backend, + unsigned int list_type, + git_reference_foreach_cb callback, + void *payload) +{ + refdb_fs_backend *backend; + int result; + struct dirent_list_data data; + git_buf refs_path = GIT_BUF_INIT; + const char *ref_name; + void *ref = NULL; + + GIT_UNUSED(ref); + + assert(_backend); + backend = (refdb_fs_backend *)_backend; + + if (packed_load(backend) < 0) + return -1; + + /* list all the packed references first */ + if (list_type & GIT_REF_OID) { + git_strmap_foreach(backend->refcache.packfile, ref_name, ref, { + if (callback(ref_name, payload)) + return GIT_EUSER; + }); + } + + /* now list the loose references, trying not to + * duplicate the ref names already in the packed-refs file */ + + data.repo_path_len = strlen(backend->path); + data.list_type = list_type; + data.backend = backend; + data.callback = callback; + data.callback_payload = payload; + data.callback_error = 0; + + if (git_buf_joinpath(&refs_path, backend->path, GIT_REFS_DIR) < 0) + return -1; + + result = git_path_direach(&refs_path, _dirent_loose_listall, &data); + + git_buf_free(&refs_path); + + return data.callback_error ? GIT_EUSER : result; +} + +static int loose_write(refdb_fs_backend *backend, const git_reference *ref) +{ + git_filebuf file = GIT_FILEBUF_INIT; + git_buf ref_path = GIT_BUF_INIT; + + /* Remove a possibly existing empty directory hierarchy + * which name would collide with the reference name + */ + if (git_futils_rmdir_r(ref->name, backend->path, + GIT_RMDIR_SKIP_NONEMPTY) < 0) + return -1; + + if (git_buf_joinpath(&ref_path, backend->path, ref->name) < 0) + return -1; + + if (git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE) < 0) { + git_buf_free(&ref_path); + return -1; + } + + git_buf_free(&ref_path); + + if (ref->type == GIT_REF_OID) { + char oid[GIT_OID_HEXSZ + 1]; + + git_oid_fmt(oid, &ref->target.oid); + oid[GIT_OID_HEXSZ] = '\0'; + + git_filebuf_printf(&file, "%s\n", oid); + + } else if (ref->type == GIT_REF_SYMBOLIC) { + git_filebuf_printf(&file, GIT_SYMREF "%s\n", ref->target.symbolic); + } else { + assert(0); /* don't let this happen */ + } + + return git_filebuf_commit(&file, GIT_REFS_FILE_MODE); +} + +static int packed_sort(const void *a, const void *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); +} + +/* + * Find out what object this reference resolves to. + * + * 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(refdb_fs_backend *backend, struct packref *ref) +{ + git_object *object; + + 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->name, GIT_REFS_TAGS_DIR) != 0) + return 0; + + /* + * Find the tagged object in the repository + */ + if (git_object_lookup(&object, backend->repo, &ref->oid, GIT_OBJ_ANY) < 0) + return -1; + + /* + * If the tagged object is a Tag object, we need to resolve it; + * if the ref is actually a 'weak' ref, we don't need to resolve + * anything. + */ + if (git_object_type(object) == GIT_OBJ_TAG) { + git_tag *tag = (git_tag *)object; + + /* + * Find the object pointed at by this tag + */ + git_oid_cpy(&ref->peel, git_tag_target_id(tag)); + ref->flags |= GIT_PACKREF_HAS_PEEL; + + /* + * The reference has now cached the resolved OID, and is + * marked at such. When written to the packfile, it'll be + * accompanied by this resolved oid + */ + } + + git_object_free(object); + return 0; +} + +/* + * Write a single reference into a packfile + */ +static int packed_write_ref(struct packref *ref, git_filebuf *file) +{ + 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. + * + * 6fa8a902cc1d18527e1355773c86721945475d37 refs/tags/libgit2-0.4 + * ^2ec0cb7959b0bf965d54f95453f5b4b34e8d3100 + * + * This obviously only applies to tags. + * The required peels have already been loaded into `ref->peel_target`. + */ + if (ref->flags & GIT_PACKREF_HAS_PEEL) { + char peel[GIT_OID_HEXSZ + 1]; + git_oid_fmt(peel, &ref->peel); + peel[GIT_OID_HEXSZ] = 0; + + if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0) + return -1; + } else { + if (git_filebuf_printf(file, "%s %s\n", oid, ref->name) < 0) + return -1; + } + + return 0; +} + +/* + * Remove all loose references + * + * Once we have successfully written a packfile, + * all the loose references that were packed must be + * removed from disk. + * + * This is a dangerous method; make sure the packfile + * is well-written, because we are destructing references + * here otherwise. + */ +static int packed_remove_loose( + refdb_fs_backend *backend, + git_vector *packing_list) +{ + size_t i; + git_buf full_path = GIT_BUF_INIT; + int failed = 0; + + for (i = 0; i < packing_list->length; ++i) { + struct packref *ref = git_vector_get(packing_list, i); + + if ((ref->flags & GIT_PACKREF_WAS_LOOSE) == 0) + continue; + + if (git_buf_joinpath(&full_path, backend->path, ref->name) < 0) + return -1; /* critical; do not try to recover on oom */ + + 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. + */ + } + + git_buf_free(&full_path); + return failed ? -1 : 0; +} + +/* + * Write all the contents in the in-memory packfile to disk. + */ +static int packed_write(refdb_fs_backend *backend) +{ + git_filebuf pack_file = GIT_FILEBUF_INIT; + size_t i; + git_buf pack_file_path = GIT_BUF_INIT; + git_vector packing_list; + unsigned int total_refs; + + assert(backend && backend->refcache.packfile); + + total_refs = + (unsigned int)git_strmap_num_entries(backend->refcache.packfile); + + if (git_vector_init(&packing_list, total_refs, packed_sort) < 0) + return -1; + + /* Load all the packfile into a vector */ + { + struct packref *reference; + + /* cannot fail: vector already has the right size */ + git_strmap_foreach_value(backend->refcache.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! */ + if (git_buf_joinpath(&pack_file_path, + backend->path, 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 (git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER) < 0) + goto cleanup_packfile; + + for (i = 0; i < packing_list.length; ++i) { + struct packref *ref = (struct packref *)git_vector_get(&packing_list, i); + + if (packed_find_peel(backend, ref) < 0) + goto cleanup_packfile; + + if (packed_write_ref(ref, &pack_file) < 0) + goto cleanup_packfile; + } + + /* if we've written all the references properly, we can commit + * the packfile to make the changes effective */ + if (git_filebuf_commit(&pack_file, GIT_PACKEDREFS_FILE_MODE) < 0) + goto cleanup_memory; + + /* when and only when the packfile has been properly written, + * we can go ahead and remove the loose refs */ + if (packed_remove_loose(backend, &packing_list) < 0) + goto cleanup_memory; + + { + struct stat st; + if (p_stat(pack_file_path.ptr, &st) == 0) + backend->refcache.packfile_time = st.st_mtime; + } + + git_vector_free(&packing_list); + git_buf_free(&pack_file_path); + + /* we're good now */ + return 0; + +cleanup_packfile: + git_filebuf_cleanup(&pack_file); + +cleanup_memory: + git_vector_free(&packing_list); + git_buf_free(&pack_file_path); + + return -1; +} + +static int refdb_fs_backend__write( + git_refdb_backend *_backend, + const git_reference *ref) +{ + refdb_fs_backend *backend; + + assert(_backend); + backend = (refdb_fs_backend *)_backend; + + return loose_write(backend, ref); +} + +static int refdb_fs_backend__delete( + git_refdb_backend *_backend, + const git_reference *ref) +{ + refdb_fs_backend *backend; + git_repository *repo; + git_buf loose_path = GIT_BUF_INIT; + struct packref *pack_ref; + khiter_t pack_ref_pos; + int error = 0, pack_error; + bool loose_deleted; + + assert(_backend); + assert(ref); + + backend = (refdb_fs_backend *)_backend; + repo = backend->repo; + + /* If a loose reference exists, remove it from the filesystem */ + + if (git_buf_joinpath(&loose_path, repo->path_repository, ref->name) < 0) + return -1; + + if (git_path_isfile(loose_path.ptr)) { + error = p_unlink(loose_path.ptr); + loose_deleted = 1; + } + + git_buf_free(&loose_path); + + if (error != 0) + return error; + + /* If a packed reference exists, remove it from the packfile and repack */ + + if ((pack_error = packed_map_entry(&pack_ref, &pack_ref_pos, backend, ref->name)) == 0) { + git_strmap_delete_at(backend->refcache.packfile, pack_ref_pos); + git__free(pack_ref); + + error = packed_write(backend); + } + + if (pack_error == GIT_ENOTFOUND) + error = loose_deleted ? 0 : GIT_ENOTFOUND; + else + error = pack_error; + + return error; +} + +static int refdb_fs_backend__compress(git_refdb_backend *_backend) +{ + refdb_fs_backend *backend; + + assert(_backend); + backend = (refdb_fs_backend *)_backend; + + if (packed_load(backend) < 0 || /* load the existing packfile */ + packed_loadloose(backend) < 0 || /* add all the loose refs */ + packed_write(backend) < 0) /* write back to disk */ + return -1; + + return 0; +} + +static void refcache_free(git_refcache *refs) +{ + assert(refs); + + if (refs->packfile) { + struct packref *reference; + + git_strmap_foreach_value(refs->packfile, reference, { + git__free(reference); + }); + + git_strmap_free(refs->packfile); + } +} + +static void refdb_fs_backend__free(git_refdb_backend *_backend) +{ + refdb_fs_backend *backend; + + assert(_backend); + backend = (refdb_fs_backend *)_backend; + + refcache_free(&backend->refcache); + git__free(backend); +} + +int git_refdb_backend_fs( + git_refdb_backend **backend_out, + git_repository *repository, + git_refdb *refdb) +{ + refdb_fs_backend *backend; + + backend = git__calloc(1, sizeof(refdb_fs_backend)); + GITERR_CHECK_ALLOC(backend); + + backend->repo = repository; + backend->path = repository->path_repository; + backend->refdb = refdb; + + backend->parent.exists = &refdb_fs_backend__exists; + backend->parent.lookup = &refdb_fs_backend__lookup; + backend->parent.foreach = &refdb_fs_backend__foreach; + backend->parent.write = &refdb_fs_backend__write; + backend->parent.delete = &refdb_fs_backend__delete; + backend->parent.compress = &refdb_fs_backend__compress; + backend->parent.free = &refdb_fs_backend__free; + + *backend_out = (git_refdb_backend *)backend; + return 0; +} diff --git a/src/refdb_fs.h b/src/refdb_fs.h new file mode 100644 index 000000000..79e296833 --- /dev/null +++ b/src/refdb_fs.h @@ -0,0 +1,15 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * 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_refdb_fs_h__ +#define INCLUDE_refdb_fs_h__ + +typedef struct { + git_strmap *packfile; + time_t packfile_time; +} git_refcache; + +#endif diff --git a/src/reflog.c b/src/reflog.c index 432680b99..8c133fe53 100644 --- a/src/reflog.c +++ b/src/reflog.c @@ -163,7 +163,7 @@ fail: void git_reflog_free(git_reflog *reflog) { - unsigned int i; + size_t i; git_reflog_entry *entry; if (reflog == NULL) diff --git a/src/refs.c b/src/refs.c index 113cadad5..dde2f51a9 100644 --- a/src/refs.c +++ b/src/refs.c @@ -11,11 +11,15 @@ #include "fileops.h" #include "pack.h" #include "reflog.h" +#include "refdb.h" #include <git2/tag.h> #include <git2/object.h> #include <git2/oid.h> #include <git2/branch.h> +#include <git2/refs.h> +#include <git2/refdb.h> +#include <git2/refdb_backend.h> GIT__USE_STRMAP; @@ -27,786 +31,55 @@ enum { GIT_PACKREF_WAS_LOOSE = 2 }; -struct packref { - git_oid oid; - git_oid peel; - char flags; - char name[GIT_FLEX_ARRAY]; -}; - -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, 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); - -/* packed refs */ -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(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_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); - -void git_reference_free(git_reference *reference) -{ - if (reference == NULL) - return; - - git__free(reference->name); - reference->name = NULL; - - if (reference->flags & GIT_REF_SYMBOLIC) { - git__free(reference->target.symbolic); - reference->target.symbolic = NULL; - } - - git__free(reference); -} - -static int reference_alloc( - git_reference **ref_out, - git_repository *repo, - const char *name) -{ - git_reference *reference = NULL; - - assert(ref_out && repo && name); - - reference = git__malloc(sizeof(git_reference)); - GITERR_CHECK_ALLOC(reference); - - memset(reference, 0x0, sizeof(git_reference)); - reference->owner = repo; - - reference->name = git__strdup(name); - GITERR_CHECK_ALLOC(reference->name); - - *ref_out = reference; - return 0; -} - -static int reference_read( - git_buf *file_content, - time_t *mtime, - const char *repo_path, - const char *ref_name, - int *updated) -{ - git_buf path = GIT_BUF_INIT; - int result; - - assert(file_content && repo_path && ref_name); - - /* Determine the full path of the file */ - if (git_buf_joinpath(&path, repo_path, ref_name) < 0) - return -1; - - result = git_futils_readbuffer_updated( - file_content, path.ptr, mtime, NULL, updated); - git_buf_free(&path); - - return result; -} - -static int loose_parse_symbolic(git_reference *ref, git_buf *file_content) -{ - const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF); - const char *refname_start; - - refname_start = (const char *)file_content->ptr; - - if (git_buf_len(file_content) < header_len + 1) { - giterr_set(GITERR_REFERENCE, "Corrupted loose reference file"); - return -1; - } - - /* - * Assume we have already checked for the header - * before calling this function - */ - refname_start += header_len; - - ref->target.symbolic = git__strdup(refname_start); - GITERR_CHECK_ALLOC(ref->target.symbolic); - - return 0; -} - -static int loose_parse_oid(git_oid *oid, git_buf *file_content) -{ - size_t len; - const char *str; - - len = git_buf_len(file_content); - if (len < GIT_OID_HEXSZ) - goto corrupted; - - /* str is guranteed to be zero-terminated */ - str = git_buf_cstr(file_content); - - /* we need to get 40 OID characters from the file */ - if (git_oid_fromstr(oid, git_buf_cstr(file_content)) < 0) - goto corrupted; - - /* If the file is longer than 40 chars, the 41st must be a space */ - str += GIT_OID_HEXSZ; - if (*str == '\0' || git__isspace(*str)) - return 0; - -corrupted: - giterr_set(GITERR_REFERENCE, "Corrupted loose reference file"); - return -1; -} - -static git_ref_t loose_guess_rtype(const git_buf *full_path) -{ - git_buf ref_file = GIT_BUF_INIT; - git_ref_t type; - - type = GIT_REF_INVALID; - - 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; - } - - git_buf_free(&ref_file); - return type; -} - -static int loose_lookup(git_reference *ref) -{ - int result, updated; - git_buf ref_file = GIT_BUF_INIT; - - result = reference_read(&ref_file, &ref->mtime, - ref->owner->path_repository, ref->name, &updated); - - if (result < 0) - return result; - - if (!updated) - return 0; - - if (ref->flags & GIT_REF_SYMBOLIC) { - git__free(ref->target.symbolic); - ref->target.symbolic = NULL; - } - - ref->flags = 0; - - if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) { - ref->flags |= GIT_REF_SYMBOLIC; - git_buf_rtrim(&ref_file); - result = loose_parse_symbolic(ref, &ref_file); - } else { - ref->flags |= GIT_REF_OID; - result = loose_parse_oid(&ref->target.oid, &ref_file); - } - - git_buf_free(&ref_file); - return result; -} - -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; - - git_buf_rtrim(&ref_file); - - 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; - } - - ref->flags = GIT_PACKREF_WAS_LOOSE; - - *ref_out = ref; - git_buf_free(&ref_file); - return 0; -} - -static int loose_write(git_reference *ref) -{ - git_filebuf file = GIT_FILEBUF_INIT; - git_buf ref_path = GIT_BUF_INIT; - struct stat st; - - /* Remove a possibly existing empty directory hierarchy - * which name would collide with the reference name - */ - if (git_futils_rmdir_r(ref->name, ref->owner->path_repository, - GIT_RMDIR_SKIP_NONEMPTY) < 0) - return -1; - - if (git_buf_joinpath(&ref_path, ref->owner->path_repository, ref->name) < 0) - return -1; - - if (git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE) < 0) { - git_buf_free(&ref_path); - return -1; - } - - git_buf_free(&ref_path); - - if (ref->flags & GIT_REF_OID) { - char oid[GIT_OID_HEXSZ + 1]; - - git_oid_fmt(oid, &ref->target.oid); - oid[GIT_OID_HEXSZ] = '\0'; - - git_filebuf_printf(&file, "%s\n", oid); - - } else if (ref->flags & GIT_REF_SYMBOLIC) { - git_filebuf_printf(&file, GIT_SYMREF "%s\n", ref->target.symbolic); - } else { - assert(0); /* don't let this happen */ - } - - if (p_stat(ref_path.ptr, &st) == 0) - ref->mtime = st.st_mtime; - - return git_filebuf_commit(&file, GIT_REFS_FILE_MODE); -} - -static int packed_parse_peel( - struct packref *tag_ref, - const char **buffer_out, - const char *buffer_end) -{ - const char *buffer = *buffer_out + 1; - - assert(buffer[-1] == '^'); - - /* Ensure it's not the first entry of the file */ - if (tag_ref == NULL) - goto corrupt; - - /* Ensure reference is a tag */ - if (git__prefixcmp(tag_ref->name, GIT_REFS_TAGS_DIR) != 0) - goto corrupt; - - if (buffer + GIT_OID_HEXSZ > buffer_end) - goto corrupt; - - /* Is this 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 != buffer_end) { - if (*buffer == '\n') - buffer++; - else - goto corrupt; - } - - *buffer_out = buffer; - return 0; - -corrupt: - giterr_set(GITERR_REFERENCE, "The packed references file is corrupted"); - return -1; -} - -static int packed_parse_oid( - struct packref **ref_out, - const char **buffer_out, - const char *buffer_end) -{ - struct packref *ref = NULL; - - const char *buffer = *buffer_out; - const char *refname_begin, *refname_end; - - size_t refname_len; - git_oid id; - - refname_begin = (buffer + GIT_OID_HEXSZ + 1); - if (refname_begin >= buffer_end || refname_begin[-1] != ' ') - goto corrupt; - - /* Is this a valid object id? */ - if (git_oid_fromstr(&id, buffer) < 0) - goto corrupt; - - refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin); - if (refname_end == NULL) - refname_end = buffer_end; - - if (refname_end[-1] == '\r') - refname_end--; - refname_len = refname_end - refname_begin; - - ref = git__malloc(sizeof(struct packref) + refname_len + 1); - GITERR_CHECK_ALLOC(ref); - - memcpy(ref->name, refname_begin, refname_len); - ref->name[refname_len] = 0; - - git_oid_cpy(&ref->oid, &id); - - ref->flags = 0; - - *ref_out = ref; - *buffer_out = refname_end + 1; - - return 0; - -corrupt: - git__free(ref); - giterr_set(GITERR_REFERENCE, "The packed references file is corrupted"); - return -1; -} - -static int packed_load(git_repository *repo) -{ - int result, updated; - git_buf packfile = GIT_BUF_INIT; - const char *buffer_start, *buffer_end; - git_refcache *ref_cache = &repo->references; - - /* First we make sure we have allocated the hash table */ - if (ref_cache->packfile == NULL) { - ref_cache->packfile = git_strmap_alloc(); - GITERR_CHECK_ALLOC(ref_cache->packfile); - } - - result = reference_read(&packfile, &ref_cache->packfile_time, - repo->path_repository, GIT_PACKEDREFS_FILE, &updated); - - /* - * 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; - } - - if (result < 0) - return -1; - - if (!updated) - return 0; - - /* - * At this point, we want to refresh the packed refs. We already - * have the contents in our buffer. - */ - git_strmap_clear(ref_cache->packfile); - - buffer_start = (const char *)packfile.ptr; - buffer_end = (const char *)(buffer_start) + packfile.size; - - while (buffer_start < buffer_end && buffer_start[0] == '#') { - buffer_start = strchr(buffer_start, '\n'); - if (buffer_start == NULL) - goto parse_failed; - - buffer_start++; - } - - while (buffer_start < buffer_end) { - int err; - struct packref *ref = NULL; - - if (packed_parse_oid(&ref, &buffer_start, buffer_end) < 0) - goto parse_failed; - - if (buffer_start[0] == '^') { - if (packed_parse_peel(ref, &buffer_start, buffer_end) < 0) - goto parse_failed; - } - - git_strmap_insert(ref_cache->packfile, ref->name, ref, err); - if (err < 0) - goto parse_failed; - } - - git_buf_free(&packfile); - return 0; - -parse_failed: - git_strmap_free(ref_cache->packfile); - ref_cache->packfile = NULL; - git_buf_free(&packfile); - return -1; -} - - -struct dirent_list_data { - git_repository *repo; - size_t repo_path_len; - unsigned int list_flags; - - int (*callback)(const char *, void *); - void *callback_payload; - int callback_error; -}; - -static int _dirent_loose_listall(void *_data, git_buf *full_path) -{ - struct dirent_list_data *data = (struct dirent_list_data *)_data; - const char *file_path = full_path->ptr + data->repo_path_len; - - if (git_path_isdir(full_path->ptr) == 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_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 0; /* we are filtering out this reference */ - } - - /* Locked references aren't returned */ - if (!git__suffixcmp(file_path, GIT_FILELOCK_EXTENSION)) - return 0; - - if (data->callback(file_path, data->callback_payload)) - data->callback_error = GIT_EUSER; - - return data->callback_error; -} - -static int _dirent_loose_load(void *data, git_buf *full_path) -{ - git_repository *repository = (git_repository *)data; - void *old_ref = NULL; - struct packref *ref; - const char *file_path; - int err; - - if (git_path_isdir(full_path->ptr) == true) - return git_path_direach(full_path, _dirent_loose_load, repository); - - file_path = full_path->ptr + strlen(repository->path_repository); - - if (loose_lookup_to_packfile(&ref, repository, file_path) < 0) - return -1; - - git_strmap_insert2( - repository->references.packfile, ref->name, ref, old_ref, err); - if (err < 0) { - git__free(ref); - return -1; - } - - git__free(old_ref); - return 0; -} - -/* - * Load all the loose references from the repository - * into the in-memory Packfile, and build a vector with - * all the references so it can be written back to - * disk. - */ -static int packed_loadloose(git_repository *repository) +git_reference *git_reference__alloc( + git_refdb *refdb, + const char *name, + const git_oid *oid, + const char *symbolic) { - git_buf refs_path = GIT_BUF_INIT; - int result; - - /* the packfile must have been previously loaded! */ - assert(repository->references.packfile); - - 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 - */ - result = git_path_direach(&refs_path, _dirent_loose_load, repository); - git_buf_free(&refs_path); - - return result; -} + git_reference *ref; + size_t namelen; -/* - * Write a single reference into a packfile - */ -static int packed_write_ref(struct packref *ref, git_filebuf *file) -{ - char oid[GIT_OID_HEXSZ + 1]; + assert(refdb && name && ((oid && !symbolic) || (!oid && symbolic))); - git_oid_fmt(oid, &ref->oid); - oid[GIT_OID_HEXSZ] = 0; + namelen = strlen(name); - /* - * For references that peel to an object in the repo, we must - * write the resulting peel on a separate line, e.g. - * - * 6fa8a902cc1d18527e1355773c86721945475d37 refs/tags/libgit2-0.4 - * ^2ec0cb7959b0bf965d54f95453f5b4b34e8d3100 - * - * This obviously only applies to tags. - * The required peels have already been loaded into `ref->peel_target`. - */ - if (ref->flags & GIT_PACKREF_HAS_PEEL) { - char peel[GIT_OID_HEXSZ + 1]; - git_oid_fmt(peel, &ref->peel); - peel[GIT_OID_HEXSZ] = 0; + if ((ref = git__calloc(1, sizeof(git_reference) + namelen + 1)) == NULL) + return NULL; - if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0) - return -1; + if (oid) { + ref->type = GIT_REF_OID; + git_oid_cpy(&ref->target.oid, oid); } else { - if (git_filebuf_printf(file, "%s %s\n", oid, ref->name) < 0) - return -1; - } - - return 0; -} - -/* - * Find out what object this reference resolves to. - * - * 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(git_repository *repo, struct packref *ref) -{ - git_object *object; - - 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->name, GIT_REFS_TAGS_DIR) != 0) - return 0; + ref->type = GIT_REF_SYMBOLIC; - /* - * Find the tagged object in the repository - */ - 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; - * if the ref is actually a 'weak' ref, we don't need to resolve - * anything. - */ - if (git_object_type(object) == GIT_OBJ_TAG) { - git_tag *tag = (git_tag *)object; - - /* - * Find the object pointed at by this tag - */ - git_oid_cpy(&ref->peel, git_tag_target_id(tag)); - ref->flags |= GIT_PACKREF_HAS_PEEL; - - /* - * The reference has now cached the resolved OID, and is - * marked at such. When written to the packfile, it'll be - * accompanied by this resolved oid - */ - } - - git_object_free(object); - return 0; -} - -/* - * Remove all loose references - * - * Once we have successfully written a packfile, - * all the loose references that were packed must be - * removed from disk. - * - * This is a dangerous method; make sure the packfile - * is well-written, because we are destructing references - * here otherwise. - */ -static int packed_remove_loose(git_repository *repo, git_vector *packing_list) -{ - unsigned int i; - git_buf full_path = GIT_BUF_INIT; - int failed = 0; - - for (i = 0; i < packing_list->length; ++i) { - struct packref *ref = git_vector_get(packing_list, i); - - if ((ref->flags & GIT_PACKREF_WAS_LOOSE) == 0) - continue; - - if (git_buf_joinpath(&full_path, repo->path_repository, ref->name) < 0) - return -1; /* critical; do not try to recover on oom */ - - 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 ((ref->target.symbolic = git__strdup(symbolic)) == NULL) { + git__free(ref); + return NULL; } - - /* - * 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. - */ } - git_buf_free(&full_path); - return failed ? -1 : 0; -} - -static int packed_sort(const void *a, const void *b) -{ - const struct packref *ref_a = (const struct packref *)a; - const struct packref *ref_b = (const struct packref *)b; + ref->db = refdb; + memcpy(ref->name, name, namelen + 1); - return strcmp(ref_a->name, ref_b->name); + return ref; } -/* - * Write all the contents in the in-memory packfile to disk. - */ -static int packed_write(git_repository *repo) +void git_reference_free(git_reference *reference) { - git_filebuf pack_file = GIT_FILEBUF_INIT; - unsigned int i; - git_buf pack_file_path = GIT_BUF_INIT; - git_vector packing_list; - unsigned int total_refs; - - assert(repo && repo->references.packfile); - - total_refs = - (unsigned int)git_strmap_num_entries(repo->references.packfile); - - if (git_vector_init(&packing_list, total_refs, packed_sort) < 0) - return -1; - - /* Load all the packfile into a vector */ - { - struct packref *reference; - - /* 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! */ - 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 (git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER) < 0) - goto cleanup_packfile; - - for (i = 0; i < packing_list.length; ++i) { - struct packref *ref = (struct packref *)git_vector_get(&packing_list, i); - - if (packed_find_peel(repo, ref) < 0) - goto cleanup_packfile; + if (reference == NULL) + return; - if (packed_write_ref(ref, &pack_file) < 0) - goto cleanup_packfile; + if (reference->type == GIT_REF_SYMBOLIC) { + git__free(reference->target.symbolic); + reference->target.symbolic = NULL; } - /* if we've written all the references properly, we can commit - * the packfile to make the changes effective */ - if (git_filebuf_commit(&pack_file, GIT_PACKEDREFS_FILE_MODE) < 0) - goto cleanup_memory; - - /* when and only when the packfile has been properly written, - * we can go ahead and remove the loose refs */ - if (packed_remove_loose(repo, &packing_list) < 0) - goto cleanup_memory; - - { - 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); - - /* we're good now */ - return 0; - -cleanup_packfile: - git_filebuf_cleanup(&pack_file); + reference->db = NULL; + reference->type = GIT_REF_INVALID; -cleanup_memory: - git_vector_free(&packing_list); - git_buf_free(&pack_file_path); - - return -1; + git__free(reference); } struct reference_available_t { @@ -863,28 +136,6 @@ static int reference_path_available( return 0; } -static int reference_exists(int *exists, git_repository *repo, const char *ref_name) -{ - git_buf ref_path = GIT_BUF_INIT; - - if (packed_load(repo) < 0) - return -1; - - if (git_buf_joinpath(&ref_path, repo->path_repository, ref_name) < 0) - return -1; - - if (git_path_isfile(ref_path.ptr) == true || - git_strmap_exists(repo->references.packfile, ref_path.ptr)) - { - *exists = 1; - } else { - *exists = 0; - } - - git_buf_free(&ref_path); - return 0; -} - /* * Check if a reference could be written to disk, based on: * @@ -900,6 +151,11 @@ static int reference_can_write( const char *previous_name, int force) { + git_refdb *refdb; + + if (git_repository_refdb__weakptr(&refdb, repo) < 0) + return -1; + /* 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) @@ -910,7 +166,7 @@ static int reference_can_write( if (!force) { int exists; - if (reference_exists(&exists, repo, refname) < 0) + if (git_refdb_exists(&exists, refdb, refname) < 0) return -1; /* We cannot proceed if the reference already exists and we're not forcing @@ -937,139 +193,9 @@ static int reference_can_write( return 0; } - -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; - } - - /* 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; - } - - pack_ref = git_strmap_value_at(packfile_refs, pos); - - ref->flags = GIT_REF_OID | GIT_REF_PACKED; - ref->mtime = ref->owner->references.packfile_time; - git_oid_cpy(&ref->target.oid, &pack_ref->oid); - - return 0; -} - -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; - } - - /* unexpected error; free the reference */ - git_reference_free(ref); - return result; -} - -/* - * 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; - - 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; - } - - packref = git_strmap_value_at(packfile_refs, pos); - git_strmap_delete_at(packfile_refs, pos); - - 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_reference *ref_in_pack; - git_buf full_path = GIT_BUF_INIT; - - if (git_buf_joinpath(&full_path, ref->owner->path_repository, ref->name) < 0) - return -1; - - result = p_unlink(full_path.ptr); - git_buf_free(&full_path); /* done with path at this point */ - - if (result < 0) { - giterr_set(GITERR_OS, "Failed to unlink '%s'", full_path.ptr); - return -1; - } - - /* When deleting a loose reference, we have to ensure that an older - * packed version of it doesn't exist */ - if (git_reference_lookup(&ref_in_pack, ref->owner, ref->name) == 0) { - assert((ref_in_pack->flags & GIT_REF_PACKED) != 0); - return git_reference_delete(ref_in_pack); - } - - giterr_clear(); - } - - return 0; -} - int git_reference_delete(git_reference *ref) { - int result = reference_delete(ref); - git_reference_free(ref); - return result; + return git_refdb_delete(ref->db, ref); } int git_reference_lookup(git_reference **ref_out, @@ -1098,8 +224,11 @@ int git_reference_lookup_resolved( const char *name, int max_nesting) { - git_reference *scan; - int result, nesting; + char scan_name[GIT_REFNAME_MAX]; + git_ref_t scan_type; + int error = 0, nesting; + git_reference *ref = NULL; + git_refdb *refdb; assert(ref_out && repo && name); @@ -1109,48 +238,39 @@ int git_reference_lookup_resolved( max_nesting = MAX_NESTING_LEVEL; else if (max_nesting < 0) max_nesting = DEFAULT_NESTING_LEVEL; + + strncpy(scan_name, name, GIT_REFNAME_MAX); + scan_type = GIT_REF_SYMBOLIC; + + if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) + return -1; - scan = git__calloc(1, sizeof(git_reference)); - GITERR_CHECK_ALLOC(scan); - - scan->name = git__calloc(GIT_REFNAME_MAX + 1, sizeof(char)); - GITERR_CHECK_ALLOC(scan->name); - - if ((result = git_reference__normalize_name_lax( - scan->name, - GIT_REFNAME_MAX, - name)) < 0) { - git_reference_free(scan); - return result; - } - - scan->target.symbolic = git__strdup(scan->name); - GITERR_CHECK_ALLOC(scan->target.symbolic); - - scan->owner = repo; - scan->flags = GIT_REF_SYMBOLIC; + if ((error = git_reference__normalize_name_lax(scan_name, GIT_REFNAME_MAX, name)) < 0) + return error; for (nesting = max_nesting; - nesting >= 0 && (scan->flags & GIT_REF_SYMBOLIC) != 0; + nesting >= 0 && scan_type == GIT_REF_SYMBOLIC; nesting--) { - if (nesting != max_nesting) - strncpy(scan->name, scan->target.symbolic, GIT_REFNAME_MAX); - - scan->mtime = 0; + if (nesting != max_nesting) { + strncpy(scan_name, ref->target.symbolic, GIT_REFNAME_MAX); + git_reference_free(ref); + } - if ((result = reference_lookup(scan)) < 0) - return result; /* lookup git_reference_free on scan already */ + if ((error = git_refdb_lookup(&ref, refdb, scan_name)) < 0) + return error; + + scan_type = ref->type; } - if ((scan->flags & GIT_REF_OID) == 0 && max_nesting != 0) { + if (scan_type != GIT_REF_OID && max_nesting != 0) { giterr_set(GITERR_REFERENCE, "Cannot resolve reference (>%u levels deep)", max_nesting); - git_reference_free(scan); + git_reference_free(ref); return -1; } - *ref_out = scan; + *ref_out = ref; return 0; } @@ -1160,20 +280,7 @@ int git_reference_lookup_resolved( git_ref_t git_reference_type(const git_reference *ref) { assert(ref); - - if (ref->flags & GIT_REF_OID) - return GIT_REF_OID; - - 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); + return ref->type; } const char *git_reference_name(const git_reference *ref) @@ -1185,14 +292,14 @@ const char *git_reference_name(const git_reference *ref) git_repository *git_reference_owner(const git_reference *ref) { assert(ref); - return ref->owner; + return ref->db->repo; } const git_oid *git_reference_target(const git_reference *ref) { assert(ref); - if ((ref->flags & GIT_REF_OID) == 0) + if (ref->type != GIT_REF_OID) return NULL; return &ref->target.oid; @@ -1202,48 +309,45 @@ const char *git_reference_symbolic_target(const git_reference *ref) { assert(ref); - if ((ref->flags & GIT_REF_SYMBOLIC) == 0) + if (ref->type != GIT_REF_SYMBOLIC) return NULL; return ref->target.symbolic; } -int git_reference_symbolic_create( +static int reference__create( git_reference **ref_out, git_repository *repo, const char *name, - const char *target, + const git_oid *oid, + const char *symbolic, int force) { char normalized[GIT_REFNAME_MAX]; + git_refdb *refdb; git_reference *ref = NULL; - int error; - - if ((error = git_reference__normalize_name_lax( - normalized, - sizeof(normalized), - name)) < 0) - return error; - - if ((error = reference_can_write(repo, normalized, NULL, force)) < 0) + int error = 0; + + if (ref_out) + *ref_out = NULL; + + if ((error = git_reference__normalize_name_lax(normalized, sizeof(normalized), name)) < 0 || + (error = reference_can_write(repo, normalized, NULL, force)) < 0 || + (error = git_repository_refdb__weakptr(&refdb, repo)) < 0) return error; - - if (reference_alloc(&ref, repo, normalized) < 0) + + if ((ref = git_reference__alloc(refdb, name, oid, symbolic)) == NULL) return -1; - ref->flags |= GIT_REF_SYMBOLIC; - - /* set the target; this will normalize the name automatically - * and write the reference on disk */ - if (git_reference_symbolic_set_target(ref, target) < 0) { + if ((error = git_refdb_write(refdb, ref)) < 0) { git_reference_free(ref); - return -1; + return error; } - if (ref_out == NULL) { + + if (ref_out == NULL) git_reference_free(ref); - } else { + else *ref_out = ref; - } return 0; } @@ -1252,232 +356,164 @@ int git_reference_create( git_reference **ref_out, git_repository *repo, const char *name, - const git_oid *id, + const git_oid *oid, int force) { - int error; - git_reference *ref = NULL; - char normalized[GIT_REFNAME_MAX]; + git_odb *odb; + int error = 0; - if ((error = git_reference__normalize_name_lax( - normalized, - sizeof(normalized), - name)) < 0) - return error; - - if ((error = reference_can_write(repo, normalized, NULL, force)) < 0) + assert(repo && name && oid); + + /* Sanity check the reference being created - target must exist. */ + if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) return error; - - 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_target(ref, id) < 0) { - git_reference_free(ref); + + if (!git_odb_exists(odb, oid)) { + giterr_set(GITERR_REFERENCE, + "Target OID for the reference doesn't exist on the repository"); return -1; } - - if (ref_out == NULL) { - git_reference_free(ref); - } else { - *ref_out = ref; - } - - return 0; + + return reference__create(ref_out, repo, name, oid, NULL, force); } -/* - * Change the OID target of a reference. - * - * For both loose and packed references, just change - * the oid in memory and (over)write the file in disk. - * - * We do not repack packed references because of performance - * reasons. - */ -int git_reference_set_target(git_reference *ref, const git_oid *id) -{ - git_odb *odb = NULL; - if ((ref->flags & GIT_REF_OID) == 0) { - giterr_set(GITERR_REFERENCE, "Cannot set OID on symbolic reference"); - return -1; - } +int git_reference_symbolic_create( + git_reference **ref_out, + git_repository *repo, + const char *name, + const char *target, + int force) +{ + char normalized[GIT_REFNAME_MAX]; + int error = 0; - assert(ref->owner); + assert(repo && name && target); + + if ((error = git_reference__normalize_name_lax( + normalized, sizeof(normalized), target)) < 0) + return error; - if (git_repository_odb__weakptr(&odb, ref->owner) < 0) - return -1; + return reference__create(ref_out, repo, name, NULL, normalized, force); +} - /* Don't let the user create references to OIDs that - * don't exist in the ODB */ - if (!git_odb_exists(odb, id)) { - giterr_set(GITERR_REFERENCE, - "Target OID for the reference doesn't exist on the repository"); +int git_reference_set_target( + git_reference **out, + git_reference *ref, + const git_oid *id) +{ + assert(out && ref && id); + + if (ref->type != GIT_REF_OID) { + giterr_set(GITERR_REFERENCE, "Cannot set OID on symbolic reference"); return -1; } - /* Update the OID value on `ref` */ - git_oid_cpy(&ref->target.oid, id); - - /* Write back to disk */ - return loose_write(ref); + return git_reference_create(out, ref->db->repo, ref->name, id, 1); } -/* - * Change the target of a symbolic reference. - * - * This is easy because symrefs cannot be inside - * a pack. We just change the target in memory - * and overwrite the file on disk. - */ -int git_reference_symbolic_set_target(git_reference *ref, const char *target) +int git_reference_symbolic_set_target( + git_reference **out, + git_reference *ref, + const char *target) { - int error; - char normalized[GIT_REFNAME_MAX]; - - if ((ref->flags & GIT_REF_SYMBOLIC) == 0) { + assert(out && ref && target); + + if (ref->type != GIT_REF_SYMBOLIC) { giterr_set(GITERR_REFERENCE, "Cannot set symbolic target on a direct reference"); return -1; } - - if ((error = git_reference__normalize_name_lax( - normalized, - sizeof(normalized), - target)) < 0) - return error; - - git__free(ref->target.symbolic); - ref->target.symbolic = git__strdup(normalized); - GITERR_CHECK_ALLOC(ref->target.symbolic); - - return loose_write(ref); + + return git_reference_symbolic_create(out, ref->db->repo, ref->name, target, 1); } -int git_reference_rename(git_reference *ref, const char *new_name, int force) +int git_reference_rename( + git_reference **out, + git_reference *ref, + const char *new_name, + int force) { - int result; unsigned int normalization_flags; - git_buf aux_path = GIT_BUF_INIT; char normalized[GIT_REFNAME_MAX]; bool should_head_be_updated = false; - - normalization_flags = ref->flags & GIT_REF_SYMBOLIC ? - GIT_REF_FORMAT_ALLOW_ONELEVEL - : GIT_REF_FORMAT_NORMAL; - - if ((result = git_reference_normalize_name( - normalized, - sizeof(normalized), - new_name, - normalization_flags)) < 0) - return result; - - if ((result = reference_can_write(ref->owner, normalized, ref->name, force)) < 0) - return result; - - /* 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; - - /* - * Check if we have to update HEAD. - */ - if ((should_head_be_updated = git_branch_is_head(ref)) < 0) - goto cleanup; - - /* - * 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; + git_reference *result = NULL; + git_oid *oid; + const char *symbolic; + int error = 0; + int reference_has_log; + + *out = NULL; + + normalization_flags = ref->type == GIT_REF_SYMBOLIC ? + GIT_REF_FORMAT_ALLOW_ONELEVEL : GIT_REF_FORMAT_NORMAL; + + if ((error = git_reference_normalize_name(normalized, sizeof(normalized), new_name, normalization_flags)) < 0 || + (error = reference_can_write(ref->db->repo, normalized, ref->name, force)) < 0) + return error; /* - * Finally we can create the new reference. + * Create the new reference. */ - if (ref->flags & GIT_REF_SYMBOLIC) { - result = git_reference_symbolic_create( - NULL, ref->owner, new_name, ref->target.symbolic, force); + if (ref->type == GIT_REF_OID) { + oid = &ref->target.oid; + symbolic = NULL; } else { - result = git_reference_create( - NULL, ref->owner, new_name, &ref->target.oid, force); + oid = NULL; + symbolic = ref->target.symbolic; } + + if ((result = git_reference__alloc(ref->db, new_name, oid, symbolic)) == NULL) + return -1; - if (result < 0) - goto rollback; + /* Check if we have to update HEAD. */ + if ((error = git_branch_is_head(ref)) < 0) + goto on_error; - /* - * Update HEAD it was poiting to the reference being renamed. - */ - if (should_head_be_updated && - git_repository_set_head(ref->owner, new_name) < 0) { - giterr_set(GITERR_REFERENCE, - "Failed to update HEAD after renaming reference"); - goto cleanup; - } - - /* - * Rename the reflog file, if it exists. - */ - if ((git_reference_has_log(ref)) && (git_reflog_rename(ref, new_name) < 0)) - goto cleanup; + should_head_be_updated = (error > 0); - /* - * Change the name of the reference given by the user. - */ - git__free(ref->name); - ref->name = git__strdup(new_name); + /* Now delete the old ref and save the new one. */ + if ((error = git_refdb_delete(ref->db, ref)) < 0) + goto on_error; + + /* Save the new reference. */ + if ((error = git_refdb_write(ref->db, result)) < 0) + goto rollback; + + /* Update HEAD it was poiting to the reference being renamed. */ + if (should_head_be_updated && (error = git_repository_set_head(ref->db->repo, new_name)) < 0) { + giterr_set(GITERR_REFERENCE, "Failed to update HEAD after renaming reference"); + goto on_error; + } - /* The reference is no longer packed */ - ref->flags &= ~GIT_REF_PACKED; + /* Rename the reflog file, if it exists. */ + reference_has_log = git_reference_has_log(ref); + if (reference_has_log < 0) { + error = reference_has_log; + goto on_error; + } + if (reference_has_log && (error = git_reflog_rename(ref, new_name)) < 0) + goto on_error; - git_buf_free(&aux_path); - return 0; + *out = result; -cleanup: - git_buf_free(&aux_path); - return -1; + return error; rollback: - /* - * Try to create the old reference again, ignore failures - */ - if (ref->flags & GIT_REF_SYMBOLIC) - git_reference_symbolic_create( - NULL, ref->owner, ref->name, ref->target.symbolic, 0); - else - git_reference_create( - NULL, ref->owner, ref->name, &ref->target.oid, 0); + git_refdb_write(ref->db, ref); - /* The reference is no longer packed */ - ref->flags &= ~GIT_REF_PACKED; +on_error: + git_reference_free(result); - git_buf_free(&aux_path); - return -1; + return error; } int git_reference_resolve(git_reference **ref_out, const git_reference *ref) { - if (ref->flags & GIT_REF_OID) - return git_reference_lookup(ref_out, ref->owner, ref->name); + if (ref->type == GIT_REF_OID) + return git_reference_lookup(ref_out, ref->db->repo, ref->name); else - return git_reference_lookup_resolved(ref_out, ref->owner, ref->target.symbolic, -1); -} - -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; - - return 0; + return git_reference_lookup_resolved(ref_out, ref->db->repo, + ref->target.symbolic, -1); } int git_reference_foreach( @@ -1486,43 +522,10 @@ int git_reference_foreach( git_reference_foreach_cb callback, void *payload) { - int result; - struct dirent_list_data data; - git_buf refs_path = GIT_BUF_INIT; - - /* list all the packed references first */ - if (list_flags & GIT_REF_PACKED) { - const char *ref_name; - void *ref = NULL; - GIT_UNUSED(ref); - - if (packed_load(repo) < 0) - return -1; - - git_strmap_foreach(repo->references.packfile, ref_name, ref, { - if (callback(ref_name, payload)) - return GIT_EUSER; - }); - } - - /* now list the loose references, trying not to - * duplicate the ref names already in the packed-refs file */ + git_refdb *refdb; + git_repository_refdb__weakptr(&refdb, repo); - data.repo_path_len = strlen(repo->path_repository); - data.list_flags = list_flags; - data.repo = repo; - data.callback = callback; - data.callback_payload = payload; - data.callback_error = 0; - - if (git_buf_joinpath(&refs_path, repo->path_repository, GIT_REFS_DIR) < 0) - return -1; - - result = git_path_direach(&refs_path, _dirent_loose_listall, &data); - - git_buf_free(&refs_path); - - return data.callback_error ? GIT_EUSER : result; + return git_refdb_foreach(refdb, list_flags, callback, payload); } static int cb__reflist_add(const char *ref, void *data) @@ -1556,26 +559,6 @@ int git_reference_list( return 0; } -int git_reference_reload(git_reference *ref) -{ - return reference_lookup(ref); -} - -void git_repository__refcache_free(git_refcache *refs) -{ - assert(refs); - - if (refs->packfile) { - struct packref *reference; - - git_strmap_foreach_value(refs->packfile, reference, { - git__free(reference); - }); - - git_strmap_free(refs->packfile); - } -} - static int is_valid_ref_char(char ch) { if ((unsigned) ch <= ' ') @@ -1667,6 +650,9 @@ int git_reference__normalize_name( process_flags = flags; current = (char *)name; + if (*current == '/') + goto cleanup; + if (normalize) git_buf_clear(buf); @@ -1795,89 +781,62 @@ 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->type != ref2->type) + return (ref1->type == GIT_REF_SYMBOLIC) ? -1 : 1; - if (ref1->flags & GIT_REF_SYMBOLIC) + if (ref1->type == GIT_REF_SYMBOLIC) return strcmp(ref1->target.symbolic, ref2->target.symbolic); return git_oid_cmp(&ref1->target.oid, &ref2->target.oid); } -/* Update the reference named `ref_name` so it points to `oid` */ -int git_reference__update(git_repository *repo, const git_oid *oid, const char *ref_name) +static int reference__update_terminal( + git_repository *repo, + const char *ref_name, + const git_oid *oid, + int nesting) { git_reference *ref; - int res; + int error = 0; - res = git_reference_lookup(&ref, repo, ref_name); + if (nesting > MAX_NESTING_LEVEL) + return GIT_ENOTFOUND; + + error = git_reference_lookup(&ref, repo, ref_name); - /* If we haven't found the reference at all, we assume we need to create - * a new reference and that's it */ - if (res == GIT_ENOTFOUND) { + /* If we haven't found the reference at all, create a new reference. */ + if (error == GIT_ENOTFOUND) { giterr_clear(); - return git_reference_create(NULL, repo, ref_name, oid, 1); + return git_reference_create(NULL, repo, ref_name, oid, 0); } - - if (res < 0) - return -1; - - /* If we have found a reference, but it's symbolic, we need to update - * the direct reference it points to */ + + if (error < 0) + return error; + + /* If the ref is a symbolic reference, follow its target. */ if (git_reference_type(ref) == GIT_REF_SYMBOLIC) { - git_reference *aux; - const char *sym_target; - - /* The target pointed at by this reference */ - sym_target = git_reference_symbolic_target(ref); - - /* 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(NULL, repo, sym_target, oid, 1); - git_reference_free(ref); - return res; - } - - /* free the original symbolic reference now; not before because - * we're using the `sym_target` pointer */ + error = reference__update_terminal(repo, git_reference_symbolic_target(ref), oid, + nesting+1); git_reference_free(ref); - - if (res < 0) - return -1; - - /* store the newly found direct reference in its place */ - ref = aux; + } else { + git_reference_free(ref); + error = git_reference_create(NULL, repo, ref_name, oid, 1); } - - /* 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_target(ref, oid); - git_reference_free(ref); - return res; + + return error; } -struct glob_cb_data { - const char *glob; - int (*callback)(const char *, void *); - void *payload; -}; - -static int fromglob_cb(const char *reference_name, void *payload) +/* + * Starting with the reference given by `ref_name`, follows symbolic + * references until a direct reference is found and updated the OID + * on that direct reference to `oid`. + */ +int git_reference__update_terminal( + git_repository *repo, + const char *ref_name, + const git_oid *oid) { - struct glob_cb_data *data = (struct glob_cb_data *)payload; - - if (!p_fnmatch(data->glob, reference_name, 0)) - return data->callback(reference_name, data->payload); - - return 0; + return reference__update_terminal(repo, ref_name, oid, 0); } int git_reference_foreach_glob( @@ -1889,16 +848,13 @@ int git_reference_foreach_glob( void *payload), void *payload) { - struct glob_cb_data data; + git_refdb *refdb; assert(repo && glob && callback); - data.glob = glob; - data.callback = callback; - data.payload = payload; + git_repository_refdb__weakptr(&refdb, repo); - return git_reference_foreach( - repo, list_flags, fromglob_cb, &data); + return git_refdb_foreach_glob(refdb, glob, list_flags, callback, payload); } int git_reference_has_log( @@ -1909,7 +865,8 @@ int git_reference_has_log( assert(ref); - if (git_buf_join_n(&path, '/', 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name) < 0) + if (git_buf_join_n(&path, '/', 3, ref->db->repo->path_repository, + GIT_REFLOG_DIR, ref->name) < 0) return -1; result = git_path_isfile(git_buf_cstr(&path)); diff --git a/src/refs.h b/src/refs.h index 7bd1ae68a..7d63c3fbd 100644 --- a/src/refs.h +++ b/src/refs.h @@ -10,6 +10,7 @@ #include "common.h" #include "git2/oid.h" #include "git2/refs.h" +#include "git2/refdb.h" #include "strmap.h" #include "buffer.h" @@ -47,28 +48,22 @@ #define GIT_REFNAME_MAX 1024 struct git_reference { - unsigned int flags; - git_repository *owner; - char *name; - time_t mtime; + git_refdb *db; + + git_ref_t type; union { git_oid oid; char *symbolic; } target; + + char name[0]; }; -typedef struct { - git_strmap *packfile; - time_t packfile_time; -} git_refcache; - -void git_repository__refcache_free(git_refcache *refs); - int git_reference__normalize_name_lax(char *buffer_out, size_t out_size, const char *name); int git_reference__normalize_name(git_buf *buf, const char *name, unsigned int flags); +int git_reference__update_terminal(git_repository *repo, const char *ref_name, const git_oid *oid); int git_reference__is_valid_name(const char *refname, unsigned int flags); -int git_reference__update(git_repository *repo, const git_oid *oid, const char *ref_name); int git_reference__is_branch(const char *ref_name); int git_reference__is_remote(const char *ref_name); diff --git a/src/remote.c b/src/remote.c index 0a1f2b856..a6f62d6a5 100644 --- a/src/remote.c +++ b/src/remote.c @@ -1175,6 +1175,7 @@ static int rename_one_remote_reference( int error = -1; git_buf new_name = GIT_BUF_INIT; git_reference *reference = NULL; + git_reference *newref = NULL; if (git_buf_printf( &new_name, @@ -1186,10 +1187,11 @@ static int rename_one_remote_reference( if (git_reference_lookup(&reference, repo, reference_name) < 0) goto cleanup; - error = git_reference_rename(reference, git_buf_cstr(&new_name), 0); + error = git_reference_rename(&newref, reference, git_buf_cstr(&new_name), 0); + git_reference_free(reference); cleanup: - git_reference_free(reference); + git_reference_free(newref); git_buf_free(&new_name); return error; } @@ -1252,7 +1254,7 @@ static int rename_fetch_refspecs( goto cleanup; /* Is it an in-memory remote? */ - if (remote->name == '\0') { + if (!remote->name) { error = (callback(git_buf_cstr(&serialized), payload) < 0) ? GIT_EUSER : 0; goto cleanup; } diff --git a/src/repository.c b/src/repository.c index 278abfaf2..0ad7449ba 100644 --- a/src/repository.c +++ b/src/repository.c @@ -8,6 +8,7 @@ #include <ctype.h> #include "git2/object.h" +#include "git2/refdb.h" #include "common.h" #include "repository.h" @@ -39,6 +40,15 @@ static void drop_odb(git_repository *repo) } } +static void drop_refdb(git_repository *repo) +{ + if (repo->_refdb != NULL) { + GIT_REFCOUNT_OWN(repo->_refdb, NULL); + git_refdb_free(repo->_refdb); + repo->_refdb = NULL; + } +} + static void drop_config(git_repository *repo) { if (repo->_config != NULL) { @@ -65,7 +75,6 @@ void git_repository_free(git_repository *repo) return; git_cache_free(&repo->objects); - git_repository__refcache_free(&repo->references); git_attr_cache_flush(repo); git_submodule_config_free(repo); @@ -75,6 +84,7 @@ void git_repository_free(git_repository *repo) drop_config(repo); drop_index(repo); drop_odb(repo); + drop_refdb(repo); git__free(repo); } @@ -600,6 +610,45 @@ void git_repository_set_odb(git_repository *repo, git_odb *odb) GIT_REFCOUNT_INC(odb); } +int git_repository_refdb__weakptr(git_refdb **out, git_repository *repo) +{ + assert(out && repo); + + if (repo->_refdb == NULL) { + int res; + + res = git_refdb_open(&repo->_refdb, repo); + + if (res < 0) + return -1; + + GIT_REFCOUNT_OWN(repo->_refdb, repo); + } + + *out = repo->_refdb; + return 0; +} + +int git_repository_refdb(git_refdb **out, git_repository *repo) +{ + if (git_repository_refdb__weakptr(out, repo) < 0) + return -1; + + GIT_REFCOUNT_INC(*out); + return 0; +} + +void git_repository_set_refdb(git_repository *repo, git_refdb *refdb) +{ + assert (repo && refdb); + + drop_refdb(repo); + + repo->_refdb = refdb; + GIT_REFCOUNT_OWN(repo->_refdb, repo); + GIT_REFCOUNT_INC(refdb); +} + int git_repository_index__weakptr(git_index **out, git_repository *repo) { assert(out && repo); diff --git a/src/repository.h b/src/repository.h index f19758fe4..cc2f8c2b8 100644 --- a/src/repository.h +++ b/src/repository.h @@ -19,8 +19,9 @@ #include "buffer.h" #include "odb.h" #include "object.h" -#include "attr.h" +#include "attrcache.h" #include "strmap.h" +#include "refdb.h" #define DOT_GIT ".git" #define GIT_DIR DOT_GIT "/" @@ -79,11 +80,11 @@ enum { /** Internal structure for repository object */ struct git_repository { git_odb *_odb; + git_refdb *_refdb; git_config *_config; git_index *_index; git_cache objects; - git_refcache references; git_attr_cache attrcache; git_strmap *submodules; @@ -112,6 +113,7 @@ int git_repository_head_tree(git_tree **tree, git_repository *repo); */ 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_refdb__weakptr(git_refdb **out, git_repository *repo); int git_repository_index__weakptr(git_index **out, git_repository *repo); /* diff --git a/src/reset.c b/src/reset.c index 700aac808..c1e1f865e 100644 --- a/src/reset.c +++ b/src/reset.c @@ -13,48 +13,10 @@ #include "git2/reset.h" #include "git2/checkout.h" #include "git2/merge.h" +#include "git2/refs.h" #define ERROR_MSG "Cannot perform reset" -static int update_head(git_repository *repo, git_object *commit) -{ - int error; - git_reference *head = NULL, *target = NULL; - - error = git_repository_head(&head, repo); - - if (error < 0 && error != GIT_EORPHANEDHEAD) - return error; - - if (error == GIT_EORPHANEDHEAD) { - giterr_clear(); - - /* - * TODO: This is a bit weak as this doesn't support chained - * symbolic references. yet. - */ - if ((error = git_reference_lookup(&head, repo, GIT_HEAD_FILE)) < 0) - goto cleanup; - - if ((error = git_reference_create( - &target, - repo, - git_reference_symbolic_target(head), - git_object_id(commit), 0)) < 0) - goto cleanup; - } else { - if ((error = git_reference_set_target(head, git_object_id(commit))) < 0) - goto cleanup; - } - - error = 0; - -cleanup: - git_reference_free(head); - git_reference_free(target); - return error; -} - int git_reset_default( git_repository *repo, git_object *target, @@ -167,7 +129,8 @@ int git_reset( } /* move HEAD to the new target */ - if ((error = update_head(repo, commit)) < 0) + if ((error = git_reference__update_terminal(repo, GIT_HEAD_FILE, + git_object_id(commit))) < 0) goto cleanup; if (reset_type == GIT_RESET_HARD) { diff --git a/src/revparse.c b/src/revparse.c index 7f1497130..2ba29383e 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -10,6 +10,7 @@ #include "common.h" #include "buffer.h" #include "tree.h" +#include "refdb.h" #include "git2.h" @@ -510,8 +511,8 @@ static int walk_and_search(git_object **out, git_revwalk *walk, regex_t *regex) while (!(error = git_revwalk_next(&oid, walk))) { - if ((error = git_object_lookup(&obj, git_revwalk_repository(walk), &oid, GIT_OBJ_COMMIT) < 0) && - (error != GIT_ENOTFOUND)) + error = git_object_lookup(&obj, git_revwalk_repository(walk), &oid, GIT_OBJ_COMMIT); + if ((error < 0) && (error != GIT_ENOTFOUND)) return -1; if (!regexec(regex, git_commit_message((git_commit*)obj), 0, NULL, 0)) { @@ -634,7 +635,7 @@ static int extract_how_many(int *n, const char *spec, size_t *pos) } while (spec[(*pos)] == kind && kind == '~'); if (git__isdigit(spec[*pos])) { - if ((git__strtol32(&parsed, spec + *pos, &end_ptr, 10) < 0) < 0) + if (git__strtol32(&parsed, spec + *pos, &end_ptr, 10) < 0) return GIT_EINVALIDSPEC; accumulated += (parsed - 1); @@ -656,7 +657,7 @@ static int object_from_reference(git_object **object, git_reference *reference) if (git_reference_resolve(&resolved, reference) < 0) return -1; - error = git_object_lookup(object, reference->owner, git_reference_target(resolved), GIT_OBJ_ANY); + error = git_object_lookup(object, reference->db->repo, git_reference_target(resolved), GIT_OBJ_ANY); git_reference_free(resolved); return error; diff --git a/src/stash.c b/src/stash.c index e78985063..355c5dc9c 100644 --- a/src/stash.c +++ b/src/stash.c @@ -645,13 +645,15 @@ int git_stash_drop( if (max == 1) { error = git_reference_delete(stash); + git_reference_free(stash); stash = NULL; } else if (index == 0) { const git_reflog_entry *entry; entry = git_reflog_entry_byindex(reflog, 0); - - error = git_reference_set_target(stash, &entry->oid_cur); + + git_reference_free(stash); + error = git_reference_create(&stash, repo, GIT_REFS_STASH_FILE, &entry->oid_cur, 1); } cleanup: diff --git a/src/status.c b/src/status.c index 282cb396b..ac6b4379b 100644 --- a/src/status.c +++ b/src/status.c @@ -80,22 +80,37 @@ static unsigned int workdir_delta2status(git_delta_t workdir_status) typedef struct { git_status_cb cb; void *payload; + const git_status_options *opts; } status_user_callback; static int status_invoke_cb( - git_diff_delta *i2h, git_diff_delta *w2i, void *payload) + git_diff_delta *h2i, git_diff_delta *i2w, void *payload) { status_user_callback *usercb = payload; const char *path = NULL; unsigned int status = 0; - if (w2i) { - path = w2i->old_file.path; - status |= workdir_delta2status(w2i->status); + if (i2w) { + path = i2w->old_file.path; + status |= workdir_delta2status(i2w->status); } - if (i2h) { - path = i2h->old_file.path; - status |= index_delta2status(i2h->status); + if (h2i) { + path = h2i->old_file.path; + status |= index_delta2status(h2i->status); + } + + /* if excluding submodules and this is a submodule everywhere */ + if (usercb->opts && + (usercb->opts->flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0) + { + bool in_tree = (h2i && h2i->status != GIT_DELTA_ADDED); + bool in_index = (h2i && h2i->status != GIT_DELTA_DELETED); + bool in_wd = (i2w && i2w->status != GIT_DELTA_DELETED); + + if ((!in_tree || h2i->old_file.mode == GIT_FILEMODE_COMMIT) && + (!in_index || h2i->new_file.mode == GIT_FILEMODE_COMMIT) && + (!in_wd || i2w->new_file.mode == GIT_FILEMODE_COMMIT)) + return 0; } return usercb->cb(path, status, usercb->payload); @@ -109,7 +124,7 @@ int git_status_foreach_ext( { int err = 0; git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT; - git_diff_list *idx2head = NULL, *wd2idx = NULL; + git_diff_list *head2idx = NULL, *idx2wd = NULL; git_tree *head = NULL; git_status_show_t show = opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR; @@ -142,34 +157,42 @@ int git_status_foreach_ext( diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS; if ((opts->flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0) diffopt.flags = diffopt.flags | GIT_DIFF_DISABLE_PATHSPEC_MATCH; - /* TODO: support EXCLUDE_SUBMODULES flag */ - - if (show != GIT_STATUS_SHOW_WORKDIR_ONLY && - (err = git_diff_tree_to_index(&idx2head, repo, head, NULL, &diffopt)) < 0) - goto cleanup; + if ((opts->flags & GIT_STATUS_OPT_RECURSE_IGNORED_DIRS) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_IGNORED_DIRS; + if ((opts->flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES; + + if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) { + err = git_diff_tree_to_index(&head2idx, repo, head, NULL, &diffopt); + if (err < 0) + goto cleanup; + } - if (show != GIT_STATUS_SHOW_INDEX_ONLY && - (err = git_diff_index_to_workdir(&wd2idx, repo, NULL, &diffopt)) < 0) - goto cleanup; + if (show != GIT_STATUS_SHOW_INDEX_ONLY) { + err = git_diff_index_to_workdir(&idx2wd, repo, NULL, &diffopt); + if (err < 0) + goto cleanup; + } usercb.cb = cb; usercb.payload = payload; + usercb.opts = opts; if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) { if ((err = git_diff__paired_foreach( - idx2head, NULL, status_invoke_cb, &usercb)) < 0) + head2idx, NULL, status_invoke_cb, &usercb)) < 0) goto cleanup; - git_diff_list_free(idx2head); - idx2head = NULL; + git_diff_list_free(head2idx); + head2idx = NULL; } - err = git_diff__paired_foreach(idx2head, wd2idx, status_invoke_cb, &usercb); + err = git_diff__paired_foreach(head2idx, idx2wd, status_invoke_cb, &usercb); cleanup: git_tree_free(head); - git_diff_list_free(idx2head); - git_diff_list_free(wd2idx); + git_diff_list_free(head2idx); + git_diff_list_free(idx2wd); if (err == GIT_EUSER) giterr_clear(); diff --git a/src/submodule.c b/src/submodule.c index 359306498..066a881cb 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -694,7 +694,7 @@ int git_submodule_open( git_buf_free(&path); /* if we have opened the submodule successfully, let's grab the HEAD OID */ - if (!error && !(submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)) { + if (!error) { if (!git_reference_name_to_id( &submodule->wd_oid, *subrepo, GIT_HEAD_FILE)) submodule->flags |= GIT_SUBMODULE_STATUS__WD_OID_VALID; @@ -1135,10 +1135,10 @@ static int load_submodule_config_from_index( const git_index_entry *entry; if ((error = git_repository_index__weakptr(&index, repo)) < 0 || - (error = git_iterator_for_index(&i, index)) < 0) + (error = git_iterator_for_index(&i, index, 0, NULL, NULL)) < 0) return error; - error = git_iterator_current(i, &entry); + error = git_iterator_current(&entry, i); while (!error && entry != NULL) { @@ -1154,7 +1154,7 @@ static int load_submodule_config_from_index( git_oid_cpy(gitmodules_oid, &entry->oid); } - error = git_iterator_advance(i, &entry); + error = git_iterator_advance(&entry, i); } git_iterator_free(i); @@ -1173,12 +1173,12 @@ static int load_submodule_config_from_head( if ((error = git_repository_head_tree(&head, repo)) < 0) return error; - if ((error = git_iterator_for_tree(&i, head)) < 0) { + if ((error = git_iterator_for_tree(&i, head, 0, NULL, NULL)) < 0) { git_tree_free(head); return error; } - error = git_iterator_current(i, &entry); + error = git_iterator_current(&entry, i); while (!error && entry != NULL) { @@ -1195,7 +1195,7 @@ static int load_submodule_config_from_head( git_oid_cpy(gitmodules_oid, &entry->oid); } - error = git_iterator_advance(i, &entry); + error = git_iterator_advance(&entry, i); } git_iterator_free(i); @@ -1497,7 +1497,7 @@ static int submodule_wd_status(unsigned int *status, git_submodule *sm) if (untracked > 0) *status |= GIT_SUBMODULE_STATUS_WD_UNTRACKED; - if ((git_diff_num_deltas(diff) - untracked) > 0) + if (git_diff_num_deltas(diff) != untracked) *status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED; git_diff_list_free(diff); @@ -131,7 +131,7 @@ int git_tag__parse_buffer(git_tag *tag, const char *buffer, size_t length) buffer = search + 1; tag->tagger = NULL; - if (*buffer != '\n') { + if (buffer < buffer_end && *buffer != '\n') { tag->tagger = git__malloc(sizeof(git_signature)); GITERR_CHECK_ALLOC(tag->tagger); @@ -376,9 +376,9 @@ on_error: int git_tag_delete(git_repository *repo, const char *tag_name) { - int error; git_reference *tag_ref; git_buf ref_name = GIT_BUF_INIT; + int error; error = retrieve_tag_reference(&tag_ref, &ref_name, repo, tag_name); @@ -387,7 +387,10 @@ int git_tag_delete(git_repository *repo, const char *tag_name) if (error < 0) return error; - return git_reference_delete(tag_ref); + if ((error = git_reference_delete(tag_ref)) == 0) + git_reference_free(tag_ref); + + return error; } int git_tag__parse(git_tag *tag, git_odb_object *obj) @@ -426,8 +429,7 @@ int git_tag_foreach(git_repository *repo, git_tag_foreach_cb cb, void *cb_data) data.cb_data = cb_data; data.repo = repo; - return git_reference_foreach( - repo, GIT_REF_OID | GIT_REF_PACKED, &tags_cb, &data); + return git_reference_foreach(repo, GIT_REF_OID, &tags_cb, &data); } typedef struct { diff --git a/src/trace.c b/src/trace.c new file mode 100644 index 000000000..159ac91cc --- /dev/null +++ b/src/trace.c @@ -0,0 +1,39 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * 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 "common.h" +#include "global.h" +#include "trace.h" +#include "git2/trace.h" + +#ifdef GIT_TRACE + +struct git_trace_data git_trace__data = {0}; + +#endif + +int git_trace_set(git_trace_level_t level, git_trace_callback callback) +{ +#ifdef GIT_TRACE + assert(level == 0 || callback != NULL); + + git_trace__data.level = level; + git_trace__data.callback = callback; + GIT_MEMORY_BARRIER; + + return 0; +#else + GIT_UNUSED(level); + GIT_UNUSED(callback); + + giterr_set(GITERR_INVALID, + "This version of libgit2 was not built with tracing."); + return -1; +#endif +} + diff --git a/src/trace.h b/src/trace.h new file mode 100644 index 000000000..f4bdff88a --- /dev/null +++ b/src/trace.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * 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_trace_h__ +#define INCLUDE_trace_h__ + +#include <stdarg.h> + +#include <git2/trace.h> +#include "buffer.h" + +#ifdef GIT_TRACE + +struct git_trace_data { + git_trace_level_t level; + git_trace_callback callback; +}; + +extern struct git_trace_data git_trace__data; + +GIT_INLINE(void) git_trace__write_fmt( + git_trace_level_t level, + const char *fmt, ...) +{ + git_trace_callback callback = git_trace__data.callback; + git_buf message = GIT_BUF_INIT; + va_list ap; + + va_start(ap, fmt); + git_buf_vprintf(&message, fmt, ap); + va_end(ap); + + callback(level, git_buf_cstr(&message)); + + git_buf_free(&message); +} + +#define git_trace_level() (git_trace__data.level) +#define git_trace(l, ...) { \ + if (git_trace__data.level >= l && \ + git_trace__data.callback != NULL) { \ + git_trace__write_fmt(l, __VA_ARGS__); \ + } \ + } + +#else + +#define git_trace_level() ((void)0) +#define git_trace(lvl, ...) ((void)0) + +#endif + +#endif diff --git a/src/transports/http.c b/src/transports/http.c index 964bafb19..eca06ead2 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -45,12 +45,14 @@ typedef struct { git_smart_subtransport_stream parent; const char *service; const char *service_url; + char *redirect_url; const char *verb; char *chunk_buffer; unsigned chunk_buffer_len; unsigned sent_request : 1, received_response : 1, - chunked : 1; + chunked : 1, + redirect_count : 3; } http_stream; typedef struct { @@ -76,6 +78,7 @@ typedef struct { git_buf parse_header_value; char parse_buffer_data[2048]; char *content_type; + char *location; git_vector www_authenticate; enum last_cb last_cb; int parse_error; @@ -126,7 +129,12 @@ static int gen_request( if (!t->path) t->path = "/"; - git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, t->path, s->service_url); + /* If we were redirected, make sure to respect that here */ + if (s->redirect_url) + git_buf_printf(buf, "%s %s HTTP/1.1\r\n", s->verb, s->redirect_url); + else + git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, t->path, s->service_url); + git_buf_puts(buf, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION ")\r\n"); git_buf_printf(buf, "Host: %s\r\n", t->host); @@ -186,17 +194,25 @@ static int on_header_ready(http_subtransport *t) { git_buf *name = &t->parse_header_name; git_buf *value = &t->parse_header_value; - char *dup; - if (!t->content_type && !strcasecmp("Content-Type", git_buf_cstr(name))) { - t->content_type = git__strdup(git_buf_cstr(value)); - GITERR_CHECK_ALLOC(t->content_type); + if (!strcasecmp("Content-Type", git_buf_cstr(name))) { + if (!t->content_type) { + t->content_type = git__strdup(git_buf_cstr(value)); + GITERR_CHECK_ALLOC(t->content_type); + } } else if (!strcmp("WWW-Authenticate", git_buf_cstr(name))) { - dup = git__strdup(git_buf_cstr(value)); + char *dup = git__strdup(git_buf_cstr(value)); GITERR_CHECK_ALLOC(dup); + git_vector_insert(&t->www_authenticate, dup); } + else if (!strcasecmp("Location", git_buf_cstr(name))) { + if (!t->location) { + t->location= git__strdup(git_buf_cstr(value)); + GITERR_CHECK_ALLOC(t->location); + } + } return 0; } @@ -279,6 +295,38 @@ static int on_headers_complete(http_parser *parser) } } + /* Check for a redirect. + * Right now we only permit a redirect to the same hostname. */ + if ((parser->status_code == 301 || + parser->status_code == 302 || + (parser->status_code == 303 && get_verb == s->verb) || + parser->status_code == 307) && + t->location) { + + if (s->redirect_count >= 7) { + giterr_set(GITERR_NET, "Too many redirects"); + return t->parse_error = PARSE_ERROR_GENERIC; + } + + if (t->location[0] != '/') { + giterr_set(GITERR_NET, "Only relative redirects are supported"); + return t->parse_error = PARSE_ERROR_GENERIC; + } + + /* Set the redirect URL on the stream. This is a transfer of + * ownership of the memory. */ + if (s->redirect_url) + git__free(s->redirect_url); + + s->redirect_url = t->location; + t->location = NULL; + + t->connected = 0; + s->redirect_count++; + + return t->parse_error = PARSE_ERROR_REPLAY; + } + /* Check for a 200 HTTP status code. */ if (parser->status_code != 200) { giterr_set(GITERR_NET, @@ -371,6 +419,9 @@ static void clear_parser_state(http_subtransport *t) git__free(t->content_type); t->content_type = NULL; + git__free(t->location); + t->location = NULL; + git_vector_foreach(&t->www_authenticate, i, entry) git__free(entry); @@ -405,6 +456,37 @@ static int write_chunk(gitno_socket *socket, const char *buffer, size_t len) return 0; } +static int http_connect(http_subtransport *t) +{ + int flags = 0; + + if (t->connected && + http_should_keep_alive(&t->parser) && + http_body_is_final(&t->parser)) + return 0; + + if (t->socket.socket) + gitno_close(&t->socket); + + if (t->use_ssl) { + int tflags; + + if (t->owner->parent.read_flags(&t->owner->parent, &tflags) < 0) + return -1; + + flags |= GITNO_CONNECT_SSL; + + if (GIT_TRANSPORTFLAGS_NO_CHECK_CERT & tflags) + flags |= GITNO_CONNECT_SSL_NO_CHECK_CERT; + } + + if (gitno_connect(&t->socket, t->host, t->port, flags) < 0) + return -1; + + t->connected = 1; + return 0; +} + static int http_stream_read( git_smart_subtransport_stream *stream, char *buffer, @@ -491,6 +573,10 @@ replay: * will have signaled us that we should replay the request. */ if (PARSE_ERROR_REPLAY == t->parse_error) { s->sent_request = 0; + + if (http_connect(t) < 0) + return -1; + goto replay; } @@ -627,6 +713,9 @@ static void http_stream_free(git_smart_subtransport_stream *stream) if (s->chunk_buffer) git__free(s->chunk_buffer); + if (s->redirect_url) + git__free(s->redirect_url); + git__free(s); } @@ -734,7 +823,7 @@ static int http_action( { http_subtransport *t = (http_subtransport *)subtransport; const char *default_port = NULL; - int flags = 0, ret; + int ret; if (!stream) return -1; @@ -761,30 +850,8 @@ static int http_action( t->path = strchr(url, '/'); } - if (!t->connected || - !http_should_keep_alive(&t->parser) || - !http_body_is_final(&t->parser)) { - - if (t->socket.socket) - gitno_close(&t->socket); - - if (t->use_ssl) { - int transport_flags; - - if (t->owner->parent.read_flags(&t->owner->parent, &transport_flags) < 0) - return -1; - - flags |= GITNO_CONNECT_SSL; - - if (GIT_TRANSPORTFLAGS_NO_CHECK_CERT & transport_flags) - flags |= GITNO_CONNECT_SSL_NO_CHECK_CERT; - } - - if (gitno_connect(&t->socket, t->host, t->port, flags) < 0) - return -1; - - t->connected = 1; - } + if (http_connect(t) < 0) + return -1; switch (action) { diff --git a/src/transports/local.c b/src/transports/local.c index 44431d587..ce89bb213 100644 --- a/src/transports/local.c +++ b/src/transports/local.c @@ -16,6 +16,7 @@ #include "git2/pack.h" #include "git2/commit.h" #include "git2/revparse.h" +#include "git2/push.h" #include "pack-objects.h" #include "refs.h" #include "posix.h" @@ -23,6 +24,8 @@ #include "buffer.h" #include "repository.h" #include "odb.h" +#include "push.h" +#include "remote.h" typedef struct { git_transport parent; @@ -79,8 +82,10 @@ static int add_ref(transport_local *t, const char *name) head = NULL; - /* If it's not an annotated tag, just get out */ - if (git_object_type(obj) != GIT_OBJ_TAG) { + /* If it's not an annotated tag, or if we're mocking + * git-receive-pack, just get out */ + if (git_object_type(obj) != GIT_OBJ_TAG || + t->direction != GIT_DIRECTION_FETCH) { git_object_free(obj); return 0; } @@ -119,14 +124,14 @@ static int store_refs(transport_local *t) 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) + git_vector_init(&t->refs, 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) + /* Add HEAD iff direction is fetch */ + if (t->direction == GIT_DIRECTION_FETCH && add_ref(t, GIT_HEAD_FILE) < 0) goto on_error; for (i = 0; i < ref_names.count; ++i) { @@ -245,6 +250,191 @@ static int local_negotiate_fetch( return 0; } +static int local_push_copy_object( + git_odb *local_odb, + git_odb *remote_odb, + git_pobject *obj) +{ + int error = 0; + git_odb_object *odb_obj = NULL; + git_odb_stream *odb_stream; + size_t odb_obj_size; + git_otype odb_obj_type; + git_oid remote_odb_obj_oid; + + /* Object already exists in the remote ODB; do nothing and return 0*/ + if (git_odb_exists(remote_odb, &obj->id)) + return 0; + + if ((error = git_odb_read(&odb_obj, local_odb, &obj->id)) < 0) + return error; + + odb_obj_size = git_odb_object_size(odb_obj); + odb_obj_type = git_odb_object_type(odb_obj); + + if ((error = git_odb_open_wstream(&odb_stream, remote_odb, + odb_obj_size, odb_obj_type)) < 0) + goto on_error; + + if (odb_stream->write(odb_stream, (char *)git_odb_object_data(odb_obj), + odb_obj_size) < 0 || + odb_stream->finalize_write(&remote_odb_obj_oid, odb_stream) < 0) { + error = -1; + } else if (git_oid_cmp(&obj->id, &remote_odb_obj_oid) != 0) { + giterr_set(GITERR_ODB, "Error when writing object to remote odb " + "during local push operation. Remote odb object oid does not " + "match local oid."); + error = -1; + } + + odb_stream->free(odb_stream); + +on_error: + git_odb_object_free(odb_obj); + return error; +} + +static int local_push_update_remote_ref( + git_repository *remote_repo, + const char *lref, + const char *rref, + git_oid *loid, + git_oid *roid) +{ + int error; + git_reference *remote_ref = NULL; + + /* rref will be NULL if it is implicit in the pushspec (e.g. 'b1:') */ + rref = rref ? rref : lref; + + if (lref) { + /* Create or update a ref */ + if ((error = git_reference_create(NULL, remote_repo, rref, loid, + !git_oid_iszero(roid))) < 0) + return error; + } else { + /* Delete a ref */ + if ((error = git_reference_lookup(&remote_ref, remote_repo, rref)) < 0) { + if (error == GIT_ENOTFOUND) + error = 0; + return error; + } + + if ((error = git_reference_delete(remote_ref)) < 0) + return error; + + git_reference_free(remote_ref); + } + + return 0; +} + +static int local_push( + git_transport *transport, + git_push *push) +{ + transport_local *t = (transport_local *)transport; + git_odb *remote_odb = NULL; + git_odb *local_odb = NULL; + git_repository *remote_repo = NULL; + push_spec *spec; + char *url = NULL; + int error; + unsigned int i; + size_t j; + + if ((error = git_repository_open(&remote_repo, push->remote->url)) < 0) + return error; + + /* We don't currently support pushing locally to non-bare repos. Proper + non-bare repo push support would require checking configs to see if + we should override the default 'don't let this happen' behavior */ + if (!remote_repo->is_bare) { + error = -1; + goto on_error; + } + + if ((error = git_repository_odb__weakptr(&remote_odb, remote_repo)) < 0 || + (error = git_repository_odb__weakptr(&local_odb, push->repo)) < 0) + goto on_error; + + for (i = 0; i < push->pb->nr_objects; i++) { + if ((error = local_push_copy_object(local_odb, remote_odb, + &push->pb->object_list[i])) < 0) + goto on_error; + } + + push->unpack_ok = 1; + + git_vector_foreach(&push->specs, j, spec) { + push_status *status; + const git_error *last; + char *ref = spec->rref ? spec->rref : spec->lref; + + status = git__calloc(sizeof(push_status), 1); + if (!status) + goto on_error; + + status->ref = git__strdup(ref); + if (!status->ref) { + git_push_status_free(status); + goto on_error; + } + + error = local_push_update_remote_ref(remote_repo, spec->lref, spec->rref, + &spec->loid, &spec->roid); + + switch (error) { + case GIT_OK: + break; + case GIT_EINVALIDSPEC: + status->msg = git__strdup("funny refname"); + break; + case GIT_ENOTFOUND: + status->msg = git__strdup("Remote branch not found to delete"); + break; + default: + last = giterr_last(); + + if (last && last->message) + status->msg = git__strdup(last->message); + else + status->msg = git__strdup("Unspecified error encountered"); + break; + } + + /* failed to allocate memory for a status message */ + if (error < 0 && !status->msg) { + git_push_status_free(status); + goto on_error; + } + + /* failed to insert the ref update status */ + if ((error = git_vector_insert(&push->status, status)) < 0) { + git_push_status_free(status); + goto on_error; + } + } + + if (push->specs.length) { + int flags = t->flags; + url = git__strdup(t->url); + + if (!url || t->parent.close(&t->parent) < 0 || + t->parent.connect(&t->parent, url, + push->remote->cred_acquire_cb, NULL, GIT_DIRECTION_PUSH, flags)) + goto on_error; + } + + error = 0; + +on_error: + git_repository_free(remote_repo); + git__free(url); + + return error; +} + typedef struct foreach_data { git_transfer_progress *stats; git_transfer_progress_callback progress_cb; @@ -379,30 +569,39 @@ static void local_cancel(git_transport *transport) static int local_close(git_transport *transport) { transport_local *t = (transport_local *)transport; + size_t i; + git_remote_head *head; t->connected = 0; - git_repository_free(t->repo); - t->repo = NULL; + + if (t->repo) { + git_repository_free(t->repo); + t->repo = NULL; + } + + git_vector_foreach(&t->refs, i, head) { + git__free(head->name); + git__free(head); + } + + git_vector_free(&t->refs); + + if (t->url) { + git__free(t->url); + t->url = 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 *head; - - assert(transport); + transport_local *t = (transport_local *)transport; - git_vector_foreach (vec, i, head) { - git__free(head->name); - git__free(head); - } - git_vector_free(vec); + /* Close the transport, if it's still open. */ + local_close(transport); - git__free(t->url); + /* Free the transport */ git__free(t); } @@ -423,6 +622,7 @@ int git_transport_local(git_transport **out, git_remote *owner, void *param) t->parent.connect = local_connect; t->parent.negotiate_fetch = local_negotiate_fetch; t->parent.download_pack = local_download_pack; + t->parent.push = local_push; t->parent.close = local_close; t->parent.free = local_free; t->parent.ls = local_ls; diff --git a/src/transports/smart.c b/src/transports/smart.c index e820488f6..bfcce0c08 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -24,7 +24,7 @@ static int git_smart__recv_cb(gitno_buffer *buf) buf->offset += bytes_read; if (t->packetsize_cb) - t->packetsize_cb((int)bytes_read, t->packetsize_payload); + t->packetsize_cb(bytes_read, t->packetsize_payload); return (int)(buf->offset - old_len); } diff --git a/src/transports/smart.h b/src/transports/smart.h index a9e894b65..c52401a3c 100644 --- a/src/transports/smart.h +++ b/src/transports/smart.h @@ -88,6 +88,7 @@ typedef git_pkt_data git_pkt_progress; typedef struct { enum git_pkt_type type; + int len; char error[GIT_FLEX_ARRAY]; } git_pkt_err; diff --git a/src/transports/smart_pkt.c b/src/transports/smart_pkt.c index 51edd9179..99da37567 100644 --- a/src/transports/smart_pkt.c +++ b/src/transports/smart_pkt.c @@ -122,6 +122,7 @@ static int err_pkt(git_pkt **out, const char *line, size_t len) GITERR_CHECK_ALLOC(pkt); pkt->type = GIT_PKT_ERR; + pkt->len = (int)len; memcpy(pkt->error, line, len); pkt->error[len] = '\0'; @@ -166,6 +167,25 @@ static int progress_pkt(git_pkt **out, const char *line, size_t len) return 0; } +static int sideband_error_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_err *pkt; + + line++; + len--; + pkt = git__malloc(sizeof(git_pkt_err) + len + 1); + GITERR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_ERR; + pkt->len = (int)len; + memcpy(pkt->error, line, len); + pkt->error[len] = '\0'; + + *out = (git_pkt *)pkt; + + return 0; +} + /* * Parse an other-ref line. */ @@ -380,6 +400,8 @@ int git_pkt_parse_line( ret = data_pkt(head, line, len); else if (*line == GIT_SIDE_BAND_PROGRESS) ret = progress_pkt(head, line, len); + else if (*line == GIT_SIDE_BAND_ERROR) + ret = sideband_error_pkt(head, line, len); else if (!git__prefixcmp(line, "ACK")) ret = ack_pkt(head, line, len); else if (!git__prefixcmp(line, "NAK")) diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c index 75494b2c7..8acedeb49 100644 --- a/src/transports/smart_protocol.c +++ b/src/transports/smart_protocol.c @@ -454,7 +454,7 @@ int git_smart__download_pack( /* We might have something in the buffer already from negotiate_fetch */ if (t->buffer.offset > 0) - t->packetsize_cb((int)t->buffer.offset, t->packetsize_payload); + t->packetsize_cb(t->buffer.offset, t->packetsize_payload); } if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 || @@ -536,7 +536,8 @@ static int gen_pktline(git_buf *buf, git_push *push) if (i == 0) { ++len; /* '\0' */ if (push->report_status) - len += strlen(GIT_CAP_REPORT_STATUS); + len += strlen(GIT_CAP_REPORT_STATUS) + 1; + len += strlen(GIT_CAP_SIDE_BAND_64K) + 1; } git_oid_fmt(old_id, &spec->roid); @@ -546,8 +547,13 @@ static int gen_pktline(git_buf *buf, git_push *push) if (i == 0) { git_buf_putc(buf, '\0'); - if (push->report_status) + /* Core git always starts their capabilities string with a space */ + if (push->report_status) { + git_buf_putc(buf, ' '); git_buf_printf(buf, GIT_CAP_REPORT_STATUS); + } + git_buf_putc(buf, ' '); + git_buf_printf(buf, GIT_CAP_SIDE_BAND_64K); } git_buf_putc(buf, '\n'); @@ -557,6 +563,74 @@ static int gen_pktline(git_buf *buf, git_push *push) return git_buf_oom(buf) ? -1 : 0; } +static int add_push_report_pkt(git_push *push, git_pkt *pkt) +{ + push_status *status; + + switch (pkt->type) { + case GIT_PKT_OK: + status = git__malloc(sizeof(push_status)); + GITERR_CHECK_ALLOC(status); + status->msg = NULL; + status->ref = git__strdup(((git_pkt_ok *)pkt)->ref); + if (!status->ref || + git_vector_insert(&push->status, status) < 0) { + git_push_status_free(status); + return -1; + } + break; + case GIT_PKT_NG: + status = git__calloc(sizeof(push_status), 1); + GITERR_CHECK_ALLOC(status); + status->ref = git__strdup(((git_pkt_ng *)pkt)->ref); + status->msg = git__strdup(((git_pkt_ng *)pkt)->msg); + if (!status->ref || !status->msg || + git_vector_insert(&push->status, status) < 0) { + git_push_status_free(status); + return -1; + } + break; + case GIT_PKT_UNPACK: + push->unpack_ok = ((git_pkt_unpack *)pkt)->unpack_ok; + break; + case GIT_PKT_FLUSH: + return GIT_ITEROVER; + default: + giterr_set(GITERR_NET, "report-status: protocol error"); + return -1; + } + + return 0; +} + +static int add_push_report_sideband_pkt(git_push *push, git_pkt_data *data_pkt) +{ + git_pkt *pkt; + const char *line = data_pkt->data, *line_end; + size_t line_len = data_pkt->len; + int error; + + while (line_len > 0) { + error = git_pkt_parse_line(&pkt, line, &line_end, line_len); + + if (error < 0) + return error; + + /* Advance in the buffer */ + line_len -= (line_end - line); + line = line_end; + + error = add_push_report_pkt(push, pkt); + + git_pkt_free(pkt); + + if (error < 0 && error != GIT_ITEROVER) + return error; + } + + return 0; +} + static int parse_report(gitno_buffer *buf, git_push *push) { git_pkt *pkt; @@ -586,46 +660,33 @@ static int parse_report(gitno_buffer *buf, git_push *push) gitno_consume(buf, line_end); - if (pkt->type == GIT_PKT_OK) { - push_status *status = git__malloc(sizeof(push_status)); - GITERR_CHECK_ALLOC(status); - status->ref = git__strdup(((git_pkt_ok *)pkt)->ref); - status->msg = NULL; - git_pkt_free(pkt); - if (git_vector_insert(&push->status, status) < 0) { - git__free(status); - return -1; - } - continue; - } + error = 0; - if (pkt->type == GIT_PKT_NG) { - push_status *status = git__malloc(sizeof(push_status)); - GITERR_CHECK_ALLOC(status); - status->ref = git__strdup(((git_pkt_ng *)pkt)->ref); - status->msg = git__strdup(((git_pkt_ng *)pkt)->msg); - git_pkt_free(pkt); - if (git_vector_insert(&push->status, status) < 0) { - git__free(status); - return -1; - } - continue; + switch (pkt->type) { + case GIT_PKT_DATA: + /* This is a sideband packet which contains other packets */ + error = add_push_report_sideband_pkt(push, (git_pkt_data *)pkt); + break; + case GIT_PKT_ERR: + giterr_set(GITERR_NET, "report-status: Error reported: %s", + ((git_pkt_err *)pkt)->error); + error = -1; + break; + case GIT_PKT_PROGRESS: + break; + default: + error = add_push_report_pkt(push, pkt); + break; } - if (pkt->type == GIT_PKT_UNPACK) { - push->unpack_ok = ((git_pkt_unpack *)pkt)->unpack_ok; - git_pkt_free(pkt); - continue; - } + git_pkt_free(pkt); - if (pkt->type == GIT_PKT_FLUSH) { - git_pkt_free(pkt); + /* add_push_report_pkt returns GIT_ITEROVER when it receives a flush */ + if (error == GIT_ITEROVER) return 0; - } - git_pkt_free(pkt); - giterr_set(GITERR_NET, "report-status: protocol error"); - return -1; + if (error < 0) + return error; } } diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index 970fa53bd..e502001cb 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -58,6 +58,7 @@ typedef struct { const char *service_url; const wchar_t *verb; HINTERNET request; + wchar_t *request_uri; char *chunk_buffer; unsigned chunk_buffer_len; HANDLE post_body; @@ -145,10 +146,10 @@ static int winhttp_stream_connect(winhttp_stream *s) winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); git_buf buf = GIT_BUF_INIT; char *proxy_url = NULL; - wchar_t url[GIT_WIN_PATH], ct[MAX_CONTENT_TYPE_LEN]; + wchar_t ct[MAX_CONTENT_TYPE_LEN]; wchar_t *types[] = { L"*/*", NULL }; BOOL peerdist = FALSE; - int error = -1; + int error = -1, wide_len; /* Prepare URL */ git_buf_printf(&buf, "%s%s", t->path, s->service_url); @@ -156,13 +157,31 @@ static int winhttp_stream_connect(winhttp_stream *s) if (git_buf_oom(&buf)) return -1; - git__utf8_to_16(url, GIT_WIN_PATH, git_buf_cstr(&buf)); + /* Convert URL to wide characters */ + wide_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + git_buf_cstr(&buf), -1, NULL, 0); + + if (!wide_len) { + giterr_set(GITERR_OS, "Failed to measure string for wide conversion"); + goto on_error; + } + + s->request_uri = git__malloc(wide_len * sizeof(wchar_t)); + + if (!s->request_uri) + goto on_error; + + if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + git_buf_cstr(&buf), -1, s->request_uri, wide_len)) { + giterr_set(GITERR_OS, "Failed to convert string to wide form"); + goto on_error; + } /* Establish request */ s->request = WinHttpOpenRequest( t->connection, s->verb, - url, + s->request_uri, NULL, WINHTTP_NO_REFERER, types, @@ -179,19 +198,36 @@ static int winhttp_stream_connect(winhttp_stream *s) if (proxy_url) { WINHTTP_PROXY_INFO proxy_info; - size_t wide_len; + wchar_t *proxy_wide; - git__utf8_to_16(url, GIT_WIN_PATH, proxy_url); + /* Convert URL to wide characters */ + wide_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + proxy_url, -1, NULL, 0); - wide_len = wcslen(url); + if (!wide_len) { + giterr_set(GITERR_OS, "Failed to measure string for wide conversion"); + goto on_error; + } + + proxy_wide = git__malloc(wide_len * sizeof(wchar_t)); + + if (!proxy_wide) + goto on_error; + + if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + proxy_url, -1, proxy_wide, wide_len)) { + giterr_set(GITERR_OS, "Failed to convert string to wide form"); + git__free(proxy_wide); + goto on_error; + } /* Strip any trailing forward slash on the proxy URL; * WinHTTP doesn't like it if one is present */ - if (L'/' == url[wide_len - 1]) - url[wide_len - 1] = L'\0'; + if (wide_len > 1 && L'/' == proxy_wide[wide_len - 2]) + proxy_wide[wide_len - 2] = L'\0'; proxy_info.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY; - proxy_info.lpszProxy = url; + proxy_info.lpszProxy = proxy_wide; proxy_info.lpszProxyBypass = NULL; if (!WinHttpSetOption(s->request, @@ -199,8 +235,11 @@ static int winhttp_stream_connect(winhttp_stream *s) &proxy_info, sizeof(WINHTTP_PROXY_INFO))) { giterr_set(GITERR_OS, "Failed to set proxy"); + git__free(proxy_wide); goto on_error; } + + git__free(proxy_wide); } /* Strip unwanted headers (X-P2P-PeerDist, X-P2P-PeerDistEx) that WinHTTP @@ -348,8 +387,15 @@ static int winhttp_stream_read( winhttp_stream *s = (winhttp_stream *)stream; winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); DWORD dw_bytes_read; + char replay_count = 0; replay: + /* Enforce a reasonable cap on the number of replays */ + if (++replay_count >= 7) { + giterr_set(GITERR_NET, "Too many redirects or authentication replays"); + return -1; + } + /* Connect if necessary */ if (!s->request && winhttp_stream_connect(s) < 0) return -1; @@ -445,10 +491,70 @@ replay: WINHTTP_HEADER_NAME_BY_INDEX, &status_code, &status_code_length, WINHTTP_NO_HEADER_INDEX)) { - giterr_set(GITERR_OS, "Failed to retreive status code"); + giterr_set(GITERR_OS, "Failed to retrieve status code"); return -1; } + /* The implementation of WinHTTP prior to Windows 7 will not + * redirect to an identical URI. Some Git hosters use self-redirects + * as part of their DoS mitigation strategy. Check first to see if we + * have a redirect status code, and that we haven't already streamed + * a post body. (We can't replay a streamed POST.) */ + if (!s->chunked && + (HTTP_STATUS_MOVED == status_code || + HTTP_STATUS_REDIRECT == status_code || + (HTTP_STATUS_REDIRECT_METHOD == status_code && + get_verb == s->verb) || + HTTP_STATUS_REDIRECT_KEEP_VERB == status_code)) { + + /* Check for Windows 7. This workaround is only necessary on + * Windows Vista and earlier. Windows 7 is version 6.1. */ + if (!git_has_win32_version(6, 1)) { + wchar_t *location; + DWORD location_length; + int redirect_cmp; + + /* OK, fetch the Location header from the redirect. */ + if (WinHttpQueryHeaders(s->request, + WINHTTP_QUERY_LOCATION, + WINHTTP_HEADER_NAME_BY_INDEX, + WINHTTP_NO_OUTPUT_BUFFER, + &location_length, + WINHTTP_NO_HEADER_INDEX) || + GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + giterr_set(GITERR_OS, "Failed to read Location header"); + return -1; + } + + location = git__malloc(location_length); + GITERR_CHECK_ALLOC(location); + + if (!WinHttpQueryHeaders(s->request, + WINHTTP_QUERY_LOCATION, + WINHTTP_HEADER_NAME_BY_INDEX, + location, + &location_length, + WINHTTP_NO_HEADER_INDEX)) { + giterr_set(GITERR_OS, "Failed to read Location header"); + git__free(location); + return -1; + } + + /* Compare the Location header with the request URI */ + redirect_cmp = wcscmp(location, s->request_uri); + git__free(location); + + if (!redirect_cmp) { + /* Replay the request */ + WinHttpCloseHandle(s->request); + s->request = NULL; + s->sent_request = 0; + + goto replay; + } + } + } + /* Handle authentication failures */ if (HTTP_STATUS_DENIED == status_code && get_verb == s->verb && t->owner->cred_acquire_cb) { @@ -560,11 +666,11 @@ static int winhttp_stream_write_single( return 0; } -static int put_uuid_string(LPWSTR buffer, DWORD buffer_len_cch) +static int put_uuid_string(LPWSTR buffer, size_t buffer_len_cch) { UUID uuid; RPC_STATUS status = UuidCreate(&uuid); - int result; + HRESULT result; if (RPC_S_OK != status && RPC_S_UUID_LOCAL_ONLY != status && @@ -573,17 +679,19 @@ static int put_uuid_string(LPWSTR buffer, DWORD buffer_len_cch) return -1; } - if (buffer_len_cch < (UUID_LENGTH_CCH + 1)) { - giterr_set(GITERR_NET, "Buffer insufficient to generate temp file name"); + if (buffer_len_cch < UUID_LENGTH_CCH + 1) { + giterr_set(GITERR_NET, "Buffer too small for name of temp file"); return -1; } - result = wsprintfW(buffer, L"%08x%04x%04x%02x%02x%02x%02x%02x%02x%02x%02x", + result = StringCbPrintfW( + buffer, buffer_len_cch, + L"%08x%04x%04x%02x%02x%02x%02x%02x%02x%02x%02x", uuid.Data1, uuid.Data2, uuid.Data3, uuid.Data4[0], uuid.Data4[1], uuid.Data4[2], uuid.Data4[3], uuid.Data4[4], uuid.Data4[5], uuid.Data4[6], uuid.Data4[7]); - if (result != UUID_LENGTH_CCH) { + if (FAILED(result)) { giterr_set(GITERR_OS, "Unable to generate name for temp file"); return -1; } @@ -602,17 +710,10 @@ static int get_temp_file(LPWSTR buffer, DWORD buffer_len_cch) len = wcslen(buffer); - /* 1 prefix character for the backslash, 1 postfix for - * the null terminator */ - if (buffer_len_cch - len < 1 + UUID_LENGTH_CCH + 1) { - giterr_set(GITERR_NET, "Buffer insufficient to generate temp file name"); - return -1; - } - - if (buffer[len - 1] != '\\') + if (buffer[len - 1] != '\\' && len < buffer_len_cch) buffer[len++] = '\\'; - if (put_uuid_string(&buffer[len], UUID_LENGTH_CCH + 1) < 0) + if (put_uuid_string(&buffer[len], (size_t)buffer_len_cch - len) < 0) return -1; return 0; @@ -752,6 +853,11 @@ static void winhttp_stream_free(git_smart_subtransport_stream *stream) s->post_body = NULL; } + if (s->request_uri) { + git__free(s->request_uri); + s->request_uri = NULL; + } + if (s->request) { WinHttpCloseHandle(s->request); s->request = NULL; @@ -881,7 +987,7 @@ static int winhttp_receivepack( { /* WinHTTP only supports Transfer-Encoding: chunked * on Windows Vista (NT 6.0) and higher. */ - s->chunked = LOBYTE(LOWORD(GetVersion())) >= 6; + s->chunked = git_has_win32_version(6, 0); if (s->chunked) s->parent.write = winhttp_stream_write_chunked; diff --git a/src/tree.c b/src/tree.c index 11123a18a..17b3c378d 100644 --- a/src/tree.c +++ b/src/tree.c @@ -55,23 +55,28 @@ static int valid_entry_name(const char *filename) strcmp(filename, DOT_GIT) != 0)); } -int git_tree_entry_cmp(const git_tree_entry *e1, const git_tree_entry *e2) +static int entry_sort_cmp(const void *a, const void *b) { + const git_tree_entry *e1 = (const git_tree_entry *)a; + const git_tree_entry *e2 = (const git_tree_entry *)b; + return git_path_cmp( e1->filename, e1->filename_len, git_tree_entry__is_tree(e1), - e2->filename, e2->filename_len, git_tree_entry__is_tree(e2)); + e2->filename, e2->filename_len, git_tree_entry__is_tree(e2), + git__strncmp); } -int git_tree_entry_icmp(const git_tree_entry *e1, const git_tree_entry *e2) +int git_tree_entry_cmp(const git_tree_entry *e1, const git_tree_entry *e2) { - return git_path_icmp( - e1->filename, e1->filename_len, git_tree_entry__is_tree(e1), - e2->filename, e2->filename_len, git_tree_entry__is_tree(e2)); + return entry_sort_cmp(e1, e2); } -static int entry_sort_cmp(const void *a, const void *b) +int git_tree_entry_icmp(const git_tree_entry *e1, const git_tree_entry *e2) { - return git_tree_entry_cmp((const git_tree_entry *)a, (const git_tree_entry *)b); + return git_path_cmp( + e1->filename, e1->filename_len, git_tree_entry__is_tree(e1), + e2->filename, e2->filename_len, git_tree_entry__is_tree(e2), + git__strncasecmp); } static git_tree_entry *alloc_entry(const char *filename) diff --git a/src/tsort.c b/src/tsort.c index 97473be91..4885e435b 100644 --- a/src/tsort.c +++ b/src/tsort.c @@ -24,7 +24,7 @@ #endif static int binsearch( - void **dst, const void *x, size_t size, git__tsort_r_cmp cmp, void *payload) + void **dst, const void *x, size_t size, git__sort_r_cmp cmp, void *payload) { int l, c, r; void *lx, *cx; @@ -71,7 +71,7 @@ static int binsearch( /* 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, git__tsort_r_cmp cmp, void *payload) + void **dst, size_t start, size_t size, git__sort_r_cmp cmp, void *payload) { size_t i; void *x; @@ -102,7 +102,7 @@ struct tsort_run { struct tsort_store { size_t alloc; - git__tsort_r_cmp cmp; + git__sort_r_cmp cmp; void *payload; void **storage; }; @@ -334,7 +334,7 @@ static ssize_t collapse(void **dst, struct tsort_run *stack, ssize_t stack_curr, while (0) void git__tsort_r( - void **dst, size_t size, git__tsort_r_cmp cmp, void *payload) + void **dst, size_t size, git__sort_r_cmp cmp, void *payload) { struct tsort_store _store, *store = &_store; struct tsort_run run_stack[128]; diff --git a/src/unix/posix.h b/src/unix/posix.h index c738b531d..f4886c5d1 100644 --- a/src/unix/posix.h +++ b/src/unix/posix.h @@ -8,6 +8,7 @@ #define INCLUDE_posix__w32_h__ #include <stdio.h> +#include <sys/param.h> #define p_lstat(p,b) lstat(p,b) #define p_readlink(a, b, c) readlink(a, b, c) diff --git a/src/util.c b/src/util.c index 059108ece..44ac1af73 100644 --- a/src/util.c +++ b/src/util.c @@ -10,6 +10,7 @@ #include <stdio.h> #include <ctype.h> #include "posix.h" +#include "fileops.h" #ifdef _MSC_VER # include <Shlwapi.h> @@ -37,14 +38,32 @@ int git_libgit2_capabilities() /* Declarations for tuneable settings */ extern size_t git_mwindow__window_size; extern size_t git_mwindow__mapped_limit; +extern size_t git_odb__cache_size; -void git_libgit2_opts(int key, ...) +static int config_level_to_futils_dir(int config_level) { + int val = -1; + + switch (config_level) { + case GIT_CONFIG_LEVEL_SYSTEM: val = GIT_FUTILS_DIR_SYSTEM; break; + case GIT_CONFIG_LEVEL_XDG: val = GIT_FUTILS_DIR_XDG; break; + case GIT_CONFIG_LEVEL_GLOBAL: val = GIT_FUTILS_DIR_GLOBAL; break; + default: + giterr_set( + GITERR_INVALID, "Invalid config path selector %d", config_level); + } + + return val; +} + +int git_libgit2_opts(int key, ...) +{ + int error = 0; va_list ap; va_start(ap, key); - switch(key) { + switch (key) { case GIT_OPT_SET_MWINDOW_SIZE: git_mwindow__window_size = va_arg(ap, size_t); break; @@ -60,9 +79,33 @@ void git_libgit2_opts(int key, ...) case GIT_OPT_GET_MWINDOW_MAPPED_LIMIT: *(va_arg(ap, size_t *)) = git_mwindow__mapped_limit; break; + + case GIT_OPT_GET_SEARCH_PATH: + if ((error = config_level_to_futils_dir(va_arg(ap, int))) >= 0) { + char *out = va_arg(ap, char *); + size_t outlen = va_arg(ap, size_t); + + error = git_futils_dirs_get_str(out, outlen, error); + } + break; + + case GIT_OPT_SET_SEARCH_PATH: + if ((error = config_level_to_futils_dir(va_arg(ap, int))) >= 0) + error = git_futils_dirs_set(error, va_arg(ap, const char *)); + break; + + case GIT_OPT_GET_ODB_CACHE_SIZE: + *(va_arg(ap, size_t *)) = git_odb__cache_size; + break; + + case GIT_OPT_SET_ODB_CACHE_SIZE: + git_odb__cache_size = va_arg(ap, size_t); + break; } va_end(ap); + + return error; } void git_strarray_free(git_strarray *array) @@ -72,6 +115,8 @@ void git_strarray_free(git_strarray *array) git__free(array->strings[i]); git__free(array->strings); + + memset(array, 0, sizeof(*array)); } int git_strarray_copy(git_strarray *tgt, const git_strarray *src) @@ -89,8 +134,10 @@ int git_strarray_copy(git_strarray *tgt, const git_strarray *src) GITERR_CHECK_ALLOC(tgt->strings); for (i = 0; i < src->count; ++i) { - tgt->strings[tgt->count] = git__strdup(src->strings[i]); + if (!src->strings[i]) + continue; + tgt->strings[tgt->count] = git__strdup(src->strings[i]); if (!tgt->strings[tgt->count]) { git_strarray_free(tgt); memset(tgt, 0, sizeof(*tgt)); @@ -607,3 +654,56 @@ size_t git__unescape(char *str) return (pos - str); } + +#if defined(GIT_WIN32) || defined(BSD) +typedef struct { + git__sort_r_cmp cmp; + void *payload; +} git__qsort_r_glue; + +static int GIT_STDLIB_CALL git__qsort_r_glue_cmp( + void *payload, const void *a, const void *b) +{ + git__qsort_r_glue *glue = payload; + return glue->cmp(a, b, glue->payload); +} +#endif + +void git__qsort_r( + void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload) +{ +#if defined(__MINGW32__) + git__insertsort_r(els, nel, elsize, NULL, cmp, payload); +#elif defined(GIT_WIN32) + git__qsort_r_glue glue = { cmp, payload }; + qsort_s(els, nel, elsize, git__qsort_r_glue_cmp, &glue); +#elif defined(BSD) + git__qsort_r_glue glue = { cmp, payload }; + qsort_r(els, nel, elsize, &glue, git__qsort_r_glue_cmp); +#else + qsort_r(els, nel, elsize, cmp, payload); +#endif +} + +void git__insertsort_r( + void *els, size_t nel, size_t elsize, void *swapel, + git__sort_r_cmp cmp, void *payload) +{ + uint8_t *base = els; + uint8_t *end = base + nel * elsize; + uint8_t *i, *j; + bool freeswap = !swapel; + + if (freeswap) + swapel = git__malloc(elsize); + + for (i = base + elsize; i < end; i += elsize) + for (j = i; j > base && cmp(j, j - elsize, payload) < 0; j -= elsize) { + memcpy(swapel, j, elsize); + memcpy(j, j - elsize, elsize); + memcpy(j - elsize, swapel, elsize); + } + + if (freeswap) + git__free(swapel); +} diff --git a/src/util.h b/src/util.h index 9dbcb6a4f..c0f271997 100644 --- a/src/util.h +++ b/src/util.h @@ -146,11 +146,17 @@ typedef int (*git__tsort_cmp)(const void *a, const void *b); extern void git__tsort(void **dst, size_t size, git__tsort_cmp cmp); -typedef int (*git__tsort_r_cmp)(const void *a, const void *b, void *payload); +typedef int (*git__sort_r_cmp)(const void *a, const void *b, void *payload); extern void git__tsort_r( - void **dst, size_t size, git__tsort_r_cmp cmp, void *payload); + void **dst, size_t size, git__sort_r_cmp cmp, void *payload); +extern void git__qsort_r( + void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload); + +extern void git__insertsort_r( + void *els, size_t nel, size_t elsize, void *swapel, + git__sort_r_cmp cmp, void *payload); /** * @param position If non-NULL, this will be set to the position where the diff --git a/src/win32/error.c b/src/win32/error.c index 3851ff099..4a9a0631f 100644 --- a/src/win32/error.c +++ b/src/win32/error.c @@ -8,34 +8,70 @@ #include "common.h" #include "error.h" +#ifdef GIT_WINHTTP +# include <winhttp.h> +#endif + +#define WC_ERR_INVALID_CHARS 0x80 + char *git_win32_get_error_message(DWORD error_code) { LPWSTR lpMsgBuf = NULL; + HMODULE hModule = NULL; + char *utf8_msg = NULL; + int utf8_size; + DWORD dwFlags = + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS; if (!error_code) return NULL; - if (FormatMessageW( - FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPWSTR)&lpMsgBuf, 0, NULL)) { - int utf8_size = WideCharToMultiByte(CP_UTF8, 0, lpMsgBuf, -1, NULL, 0, NULL, NULL); - - char *lpMsgBuf_utf8 = git__malloc(utf8_size * sizeof(char)); - if (lpMsgBuf_utf8 == NULL) { - LocalFree(lpMsgBuf); - return NULL; +#ifdef GIT_WINHTTP + /* Errors raised by WinHTTP are not in the system resource table */ + if (error_code >= WINHTTP_ERROR_BASE && + error_code <= WINHTTP_ERROR_LAST) + hModule = GetModuleHandleW(L"winhttp"); +#endif + + GIT_UNUSED(hModule); + + if (hModule) + dwFlags |= FORMAT_MESSAGE_FROM_HMODULE; + else + dwFlags |= FORMAT_MESSAGE_FROM_SYSTEM; + + if (FormatMessageW(dwFlags, hModule, error_code, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPWSTR)&lpMsgBuf, 0, NULL)) { + + /* Invalid code point check supported on Vista+ only */ + if (git_has_win32_version(6, 0)) + dwFlags = WC_ERR_INVALID_CHARS; + else + dwFlags = 0; + + utf8_size = WideCharToMultiByte(CP_UTF8, dwFlags, + lpMsgBuf, -1, NULL, 0, NULL, NULL); + + if (!utf8_size) { + assert(0); + goto on_error; } - if (!WideCharToMultiByte(CP_UTF8, 0, lpMsgBuf, -1, lpMsgBuf_utf8, utf8_size, NULL, NULL)) { - LocalFree(lpMsgBuf); - git__free(lpMsgBuf_utf8); - return NULL; + + utf8_msg = git__malloc(utf8_size); + + if (!utf8_msg) + goto on_error; + + if (!WideCharToMultiByte(CP_UTF8, dwFlags, + lpMsgBuf, -1, utf8_msg, utf8_size, NULL, NULL)) { + git__free(utf8_msg); + goto on_error; } +on_error: LocalFree(lpMsgBuf); - return lpMsgBuf_utf8; } - return NULL; + + return utf8_msg; } diff --git a/src/win32/findfile.c b/src/win32/findfile.c index 6fc7c7513..bc36b6b45 100644 --- a/src/win32/findfile.c +++ b/src/win32/findfile.c @@ -10,23 +10,34 @@ #include "findfile.h" #define REG_MSYSGIT_INSTALL_LOCAL L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1" + #ifndef _WIN64 #define REG_MSYSGIT_INSTALL REG_MSYSGIT_INSTALL_LOCAL #else #define REG_MSYSGIT_INSTALL L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1" #endif -int win32_expand_path(struct win32_path *s_root, const wchar_t *templ) +int git_win32__expand_path(struct git_win32__path *s_root, const wchar_t *templ) { s_root->len = ExpandEnvironmentStringsW(templ, s_root->path, MAX_PATH); return s_root->len ? 0 : -1; } -int win32_find_file(git_buf *path, const struct win32_path *root, const char *filename) +static int win32_path_utf16_to_8(git_buf *path_utf8, const wchar_t *path_utf16) +{ + char temp_utf8[GIT_PATH_MAX]; + + git__utf16_to_8(temp_utf8, path_utf16); + git_path_mkposix(temp_utf8); + + return git_buf_sets(path_utf8, temp_utf8); +} + +int git_win32__find_file( + git_buf *path, const struct git_win32__path *root, const char *filename) { size_t len, alloc_len; wchar_t *file_utf16 = NULL; - char file_utf8[GIT_PATH_MAX]; if (!root || !filename || (len = strlen(filename)) == 0) return GIT_ENOTFOUND; @@ -50,15 +61,13 @@ int win32_find_file(git_buf *path, const struct win32_path *root, const char *fi return GIT_ENOTFOUND; } - git__utf16_to_8(file_utf8, file_utf16); - git_path_mkposix(file_utf8); - git_buf_sets(path, file_utf8); - + win32_path_utf16_to_8(path, file_utf16); git__free(file_utf16); + return 0; } -wchar_t* win32_nextpath(wchar_t *path, wchar_t *buf, size_t buflen) +static wchar_t* win32_walkpath(wchar_t *path, wchar_t *buf, size_t buflen) { wchar_t term, *base = path; @@ -77,80 +86,153 @@ wchar_t* win32_nextpath(wchar_t *path, wchar_t *buf, size_t buflen) return (path != base) ? path : NULL; } -int win32_find_system_file_using_path(git_buf *path, const char *filename) +static int win32_find_git_in_path(git_buf *buf, const wchar_t *gitexe) { - wchar_t * env = NULL; - struct win32_path root; + wchar_t *env = _wgetenv(L"PATH"), lastch; + struct git_win32__path root; + size_t gitexe_len = wcslen(gitexe); - env = _wgetenv(L"PATH"); if (!env) return -1; - // search in all paths defined in PATH - while ((env = win32_nextpath(env, root.path, MAX_PATH - 1)) != NULL && *root.path) - { - wchar_t * pfin = root.path + wcslen(root.path) - 1; // last char of the current path entry + while ((env = win32_walkpath(env, root.path, MAX_PATH-1)) && *root.path) { + root.len = (DWORD)wcslen(root.path); + lastch = root.path[root.len - 1]; + + /* ensure trailing slash (MAX_PATH-1 to walkpath guarantees space) */ + if (lastch != L'/' && lastch != L'\\') { + root.path[root.len++] = L'\\'; + root.path[root.len] = L'\0'; + } - // ensure trailing slash - if (*pfin != L'/' && *pfin != L'\\') - wcscpy(++pfin, L"\\"); // we have enough space left, MAX_PATH - 1 is used in nextpath above + if (root.len + gitexe_len >= MAX_PATH) + continue; + wcscpy(&root.path[root.len], gitexe); - root.len = (DWORD)wcslen(root.path) + 1; + if (_waccess(root.path, F_OK) == 0 && root.len > 5) { + /* replace "bin\\" or "cmd\\" with "etc\\" */ + wcscpy(&root.path[root.len - 4], L"etc\\"); - if (win32_find_file(path, &root, "git.cmd") == 0 || win32_find_file(path, &root, "git.exe") == 0) { - // we found the cmd or bin directory of a git installaton - if (root.len > 5) { - wcscpy(root.path + wcslen(root.path) - 4, L"etc\\"); - if (win32_find_file(path, &root, filename) == 0) - return 0; - } + win32_path_utf16_to_8(buf, root.path); + return 0; } } - + return GIT_ENOTFOUND; } -int win32_find_system_file_using_registry(git_buf *path, const char *filename) +static int win32_find_git_in_registry( + git_buf *buf, const HKEY hieve, const wchar_t *key) { - struct win32_path root; + HKEY hKey; + DWORD dwType = REG_SZ; + struct git_win32__path path16; + + assert(buf); + + path16.len = 0; + + if (RegOpenKeyExW(hieve, key, 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS) { + if (RegQueryValueExW(hKey, L"InstallLocation", NULL, &dwType, + (LPBYTE)&path16.path, &path16.len) == ERROR_SUCCESS) + { + /* InstallLocation points to the root of the git directory */ - if (win32_find_msysgit_in_registry(&root, HKEY_CURRENT_USER, REG_MSYSGIT_INSTALL_LOCAL)) { - if (win32_find_msysgit_in_registry(&root, HKEY_LOCAL_MACHINE, REG_MSYSGIT_INSTALL)) { - giterr_set(GITERR_OS, "Cannot locate the system's msysgit directory"); - return -1; + if (path16.len + 4 > MAX_PATH) { /* 4 = wcslen(L"etc\\") */ + giterr_set(GITERR_OS, "Cannot locate git - path too long"); + return -1; + } + + wcscat(path16.path, L"etc\\"); + path16.len += 4; + + win32_path_utf16_to_8(buf, path16.path); } - } - if (win32_find_file(path, &root, filename) < 0) { - giterr_set(GITERR_OS, "The system file '%s' doesn't exist", filename); - git_buf_clear(path); - return GIT_ENOTFOUND; + RegCloseKey(hKey); } - return 0; + return path16.len ? 0 : GIT_ENOTFOUND; } -int win32_find_msysgit_in_registry(struct win32_path *root, const HKEY hieve, const wchar_t *key) +static int win32_find_existing_dirs( + git_buf *out, const wchar_t *tmpl[], char *temp[]) { - HKEY hKey; - DWORD dwType = REG_SZ; - DWORD dwSize = MAX_PATH; + struct git_win32__path path16; + git_buf buf = GIT_BUF_INIT; - assert(root); + git_buf_clear(out); - root->len = 0; - if (RegOpenKeyExW(hieve, key, 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS) { - if (RegQueryValueExW(hKey, L"InstallLocation", NULL, &dwType, (LPBYTE)&root->path, &dwSize) == ERROR_SUCCESS) { - // InstallLocation points to the root of the msysgit directory - if (dwSize + 4 > MAX_PATH) {// 4 = wcslen(L"etc\\") - giterr_set(GITERR_OS, "Cannot locate the system's msysgit directory - path too long"); - return -1; - } - wcscat(root->path, L"etc\\"); - root->len = (DWORD)wcslen(root->path) + 1; + for (; *tmpl != NULL; tmpl++) { + if (!git_win32__expand_path(&path16, *tmpl) && + path16.path[0] != L'%' && + !_waccess(path16.path, F_OK)) + { + win32_path_utf16_to_8(&buf, path16.path); + + if (buf.size) + git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr); } } - RegCloseKey(hKey); - return root->len ? 0 : GIT_ENOTFOUND; + git_buf_free(&buf); + + return (git_buf_oom(out) ? -1 : 0); +} + +int git_win32__find_system_dirs(git_buf *out) +{ + git_buf buf = GIT_BUF_INIT; + + /* directories where git.exe & git.cmd are found */ + if (!win32_find_git_in_path(&buf, L"git.exe") && buf.size) + git_buf_set(out, buf.ptr, buf.size); + else + git_buf_clear(out); + + if (!win32_find_git_in_path(&buf, L"git.cmd") && buf.size) + git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr); + + /* directories where git is installed according to registry */ + if (!win32_find_git_in_registry( + &buf, HKEY_CURRENT_USER, REG_MSYSGIT_INSTALL_LOCAL) && buf.size) + git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr); + + if (!win32_find_git_in_registry( + &buf, HKEY_LOCAL_MACHINE, REG_MSYSGIT_INSTALL) && buf.size) + git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr); + + git_buf_free(&buf); + + return (git_buf_oom(out) ? -1 : 0); +} + +int git_win32__find_global_dirs(git_buf *out) +{ + char *temp[3]; + static const wchar_t *global_tmpls[4] = { + L"%HOME%\\", + L"%HOMEDRIVE%%HOMEPATH%\\", + L"%USERPROFILE%\\", + NULL, + }; + + return win32_find_existing_dirs(out, global_tmpls, temp); +} + +int git_win32__find_xdg_dirs(git_buf *out) +{ + char *temp[6]; + static const wchar_t *global_tmpls[7] = { + L"%XDG_CONFIG_HOME%\\git", + L"%APPDATA%\\git", + L"%LOCALAPPDATA%\\git", + L"%HOME%\\.config\\git", + L"%HOMEDRIVE%%HOMEPATH%\\.config\\git", + L"%USERPROFILE%\\.config\\git", + NULL, + }; + + return win32_find_existing_dirs(out, global_tmpls, temp); } + diff --git a/src/win32/findfile.h b/src/win32/findfile.h index 47fe71596..fc79e1b72 100644 --- a/src/win32/findfile.h +++ b/src/win32/findfile.h @@ -8,17 +8,20 @@ #ifndef INCLUDE_git_findfile_h__ #define INCLUDE_git_findfile_h__ -struct win32_path { +struct git_win32__path { wchar_t path[MAX_PATH]; DWORD len; }; -int win32_expand_path(struct win32_path *s_root, const wchar_t *templ); +extern int git_win32__expand_path( + struct git_win32__path *s_root, const wchar_t *templ); -int win32_find_file(git_buf *path, const struct win32_path *root, const char *filename); -int win32_find_system_file_using_path(git_buf *path, const char *filename); -int win32_find_system_file_using_registry(git_buf *path, const char *filename); -int win32_find_msysgit_in_registry(struct win32_path *root, const HKEY hieve, const wchar_t *key); +extern int git_win32__find_file( + git_buf *path, const struct git_win32__path *root, const char *filename); + +extern int git_win32__find_system_dirs(git_buf *out); +extern int git_win32__find_global_dirs(git_buf *out); +extern int git_win32__find_xdg_dirs(git_buf *out); #endif diff --git a/src/win32/version.h b/src/win32/version.h new file mode 100644 index 000000000..483962b57 --- /dev/null +++ b/src/win32/version.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * 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_win32_version_h__ +#define INCLUDE_win32_version_h__ + +#include <windows.h> + +GIT_INLINE(int) git_has_win32_version(int major, int minor) +{ + WORD wVersion = LOWORD(GetVersion()); + + return LOBYTE(wVersion) > major || + (LOBYTE(wVersion) == major && HIBYTE(wVersion) >= minor); +} + +#endif |